use crate::error::SpatialResult;
use scirs2_core::ndarray::Array2;
pub(crate) fn bowyer_watson_3d(points: &Array2<f64>) -> SpatialResult<Vec<Vec<usize>>> {
let npoints = points.nrows();
let mut min_coords = [f64::INFINITY; 3];
let mut max_coords = [f64::NEG_INFINITY; 3];
for i in 0..npoints {
for j in 0..3 {
min_coords[j] = min_coords[j].min(points[[i, j]]);
max_coords[j] = max_coords[j].max(points[[i, j]]);
}
}
let dx = max_coords[0] - min_coords[0];
let dy = max_coords[1] - min_coords[1];
let dz = max_coords[2] - min_coords[2];
let delta_max = dx.max(dy).max(dz);
let mid = [
(min_coords[0] + max_coords[0]) / 2.0,
(min_coords[1] + max_coords[1]) / 2.0,
(min_coords[2] + max_coords[2]) / 2.0,
];
let margin = 100.0;
let super_p0 = [
mid[0] - margin * delta_max,
mid[1] - margin * delta_max,
mid[2] - margin * delta_max,
];
let super_p1 = [
mid[0] + margin * delta_max,
mid[1] - margin * delta_max,
mid[2] - margin * delta_max,
];
let super_p2 = [
mid[0],
mid[1] + margin * delta_max,
mid[2] - margin * delta_max,
];
let super_p3 = [mid[0], mid[1], mid[2] + margin * delta_max];
let mut all_points: Vec<[f64; 3]> = Vec::with_capacity(npoints + 4);
for i in 0..npoints {
all_points.push([points[[i, 0]], points[[i, 1]], points[[i, 2]]]);
}
all_points.push(super_p0);
all_points.push(super_p1);
all_points.push(super_p2);
all_points.push(super_p3);
let mut tetrahedra: Vec<[usize; 4]> = vec![[npoints, npoints + 1, npoints + 2, npoints + 3]];
for point_idx in 0..npoints {
let point = all_points[point_idx];
let mut bad_tetrahedra: Vec<usize> = Vec::new();
for (tet_idx, tet) in tetrahedra.iter().enumerate() {
let p0 = all_points[tet[0]];
let p1 = all_points[tet[1]];
let p2 = all_points[tet[2]];
let p3 = all_points[tet[3]];
if point_in_circumsphere_3d(point, p0, p1, p2, p3) {
bad_tetrahedra.push(tet_idx);
}
}
let mut boundary_faces: Vec<[usize; 3]> = Vec::new();
for &tet_idx in &bad_tetrahedra {
let tet = tetrahedra[tet_idx];
let faces = [
[tet[0], tet[1], tet[2]],
[tet[0], tet[1], tet[3]],
[tet[0], tet[2], tet[3]],
[tet[1], tet[2], tet[3]],
];
for face in &faces {
let mut is_shared = false;
for &other_idx in &bad_tetrahedra {
if other_idx == tet_idx {
continue;
}
if tetrahedron_has_face(&tetrahedra[other_idx], face) {
is_shared = true;
break;
}
}
if !is_shared {
boundary_faces.push(*face);
}
}
}
bad_tetrahedra.sort_by(|a, b| b.cmp(a));
for tet_idx in bad_tetrahedra {
tetrahedra.remove(tet_idx);
}
for face in boundary_faces {
tetrahedra.push([point_idx, face[0], face[1], face[2]]);
}
}
let super_vertices = [npoints, npoints + 1, npoints + 2, npoints + 3];
tetrahedra.retain(|tet| !tet.iter().any(|&v| super_vertices.contains(&v)));
let simplices: Vec<Vec<usize>> = tetrahedra.iter().map(|t| t.to_vec()).collect();
Ok(simplices)
}
fn tetrahedron_has_face(tet: &[usize; 4], face: &[usize; 3]) -> bool {
let faces = [
[tet[0], tet[1], tet[2]],
[tet[0], tet[1], tet[3]],
[tet[0], tet[2], tet[3]],
[tet[1], tet[2], tet[3]],
];
let mut sorted_face: Vec<usize> = face.to_vec();
sorted_face.sort();
for f in &faces {
let mut sorted_f: Vec<usize> = f.to_vec();
sorted_f.sort();
if sorted_f == sorted_face {
return true;
}
}
false
}
fn point_in_circumsphere_3d(
p: [f64; 3],
a: [f64; 3],
b: [f64; 3],
c: [f64; 3],
d: [f64; 3],
) -> bool {
let ax = a[0] - p[0];
let ay = a[1] - p[1];
let az = a[2] - p[2];
let bx = b[0] - p[0];
let by = b[1] - p[1];
let bz = b[2] - p[2];
let cx = c[0] - p[0];
let cy = c[1] - p[1];
let cz = c[2] - p[2];
let dx = d[0] - p[0];
let dy = d[1] - p[1];
let dz = d[2] - p[2];
let as_ = ax * ax + ay * ay + az * az;
let bs = bx * bx + by * by + bz * bz;
let cs = cx * cx + cy * cy + cz * cz;
let ds = dx * dx + dy * dy + dz * dz;
let det = as_ * det3x3(bx, by, bz, cx, cy, cz, dx, dy, dz)
- bs * det3x3(ax, ay, az, cx, cy, cz, dx, dy, dz)
+ cs * det3x3(ax, ay, az, bx, by, bz, dx, dy, dz)
- ds * det3x3(ax, ay, az, bx, by, bz, cx, cy, cz);
let orientation = det3x3(
b[0] - a[0],
b[1] - a[1],
b[2] - a[2],
c[0] - a[0],
c[1] - a[1],
c[2] - a[2],
d[0] - a[0],
d[1] - a[1],
d[2] - a[2],
);
if orientation > 0.0 {
det > 0.0
} else {
det < 0.0
}
}
fn det3x3(
a11: f64,
a12: f64,
a13: f64,
a21: f64,
a22: f64,
a23: f64,
a31: f64,
a32: f64,
a33: f64,
) -> f64 {
a11 * (a22 * a33 - a23 * a32) - a12 * (a21 * a33 - a23 * a31) + a13 * (a21 * a32 - a22 * a31)
}