use super::{MeshEnclosure, VolumeMesh, VolumeMeshParameters};
use crate::bounding_volume::{Aabb, BoundingVolume};
#[cfg(not(feature = "std"))]
use crate::math::ComplexField;
use crate::math::{Pose, Real, Vector};
use crate::query::PointQueryWithLocation;
use crate::shape::{SupportMap, TriMesh, TriMeshFlags};
use crate::utils::hashmap::HashMap;
use alloc::vec::Vec;
#[cfg(feature = "parallel")]
pub(super) fn par_map<T: Sync, R: Send, F: Fn(&T) -> R + Sync + Send>(items: &[T], f: F) -> Vec<R> {
use rayon::prelude::*;
items.par_iter().map(f).collect()
}
#[cfg(not(feature = "parallel"))]
pub(super) fn par_map<T, R, F: Fn(&T) -> R>(items: &[T], f: F) -> Vec<R> {
items.iter().map(f).collect()
}
pub(super) struct PointsSupportMap<'a>(pub &'a [Vector]);
impl SupportMap for PointsSupportMap<'_> {
fn local_support_point(&self, dir: Vector) -> Vector {
let mut best = self.0[0];
for pt in &self.0[1..] {
if pt.dot(dir) > best.dot(dir) {
best = *pt;
}
}
best
}
}
pub(super) fn convex_points_intersect(points: &[Vector], other: &impl SupportMap) -> bool {
crate::query::details::intersection_test_support_map_support_map(
&Pose::IDENTITY,
&PointsSupportMap(points),
other,
)
}
pub(super) struct MeshOracle<'a> {
mesh: &'a TriMesh,
}
impl<'a> MeshOracle<'a> {
pub fn new(mesh: &'a TriMesh) -> Self {
Self { mesh }
}
}
impl MeshOracle<'_> {
pub fn signed_distance(&self, pt: Vector) -> Real {
signed_distance(self.mesh, pt)
}
pub fn crosses_region(&self, region: &Aabb) -> bool {
let corners = [
region.mins,
Vector::new(region.maxs.x, region.mins.y, region.mins.z),
Vector::new(region.mins.x, region.maxs.y, region.mins.z),
Vector::new(region.maxs.x, region.maxs.y, region.mins.z),
Vector::new(region.mins.x, region.mins.y, region.maxs.z),
Vector::new(region.maxs.x, region.mins.y, region.maxs.z),
Vector::new(region.mins.x, region.maxs.y, region.maxs.z),
region.maxs,
];
let crossed = self
.mesh
.bvh()
.intersect_aabb(region)
.any(|tri| convex_points_intersect(&corners, &self.mesh.triangle(tri)));
crossed
}
pub fn surface_intersects(&self, tet: &[Vector; 4]) -> bool {
let aabb = Aabb::from_points(tet.iter().copied());
let crossed = self
.mesh
.bvh()
.intersect_aabb(&aabb)
.any(|tri| convex_points_intersect(tet, &self.mesh.triangle(tri)));
crossed
}
pub fn collect(&self, region: &Aabb, out: &mut Vec<u32>) {
out.extend(self.mesh.bvh().intersect_aabb(region));
}
pub fn crosses_among(&self, triangle: &[Vector; 3], primitives: &[u32]) -> bool {
let aabb = Aabb::from_points(triangle.iter().copied());
primitives.iter().any(|tri| {
let candidate = self.mesh.triangle(*tri);
let lows = candidate.a.min(candidate.b).min(candidate.c);
let highs = candidate.a.max(candidate.b).max(candidate.c);
lows.cmple(aabb.maxs).all()
&& highs.cmpge(aabb.mins).all()
&& convex_points_intersect(triangle, &candidate)
})
}
pub fn project_among(&self, pt: Vector, primitives: &[u32]) -> Option<Vector> {
use crate::query::PointQuery;
let mut best = None;
let mut best_dist = Real::MAX;
for tri in primitives {
let candidate = self.mesh.triangle(*tri);
let lows = candidate.a.min(candidate.b).min(candidate.c);
let highs = candidate.a.max(candidate.b).max(candidate.c);
if (pt.clamp(lows, highs) - pt).length() >= best_dist {
continue;
}
let projection = candidate.project_local_point(pt, true).point;
let dist = (projection - pt).length();
if dist < best_dist {
best = Some(projection);
best_dist = dist;
}
}
best
}
}
struct Lattice {
origin: Vector,
half_cell: Real,
dims: [i32; 3],
num_even: [i32; 3],
num_odd: [i32; 3],
corners_len: usize,
}
impl Lattice {
fn covering(aabb: Aabb, cell_size: Real) -> Self {
let half_cell = cell_size * 0.5;
let origin = aabb.mins - Vector::splat(cell_size);
let dims = core::array::from_fn(|i| {
let extent = aabb.maxs[i] - aabb.mins[i] + cell_size * 2.0;
((extent / half_cell).ceil() as i32).max(2)
});
let num_even = core::array::from_fn(|i: usize| dims[i] / 2 + 1);
let num_odd = core::array::from_fn(|i: usize| (dims[i] + 1) / 2);
let corners_len = (num_even[0] * num_even[1] * num_even[2]) as usize;
Self {
origin,
half_cell,
dims,
num_even,
num_odd,
corners_len,
}
}
fn len(&self) -> usize {
self.corners_len + (self.num_odd[0] * self.num_odd[1] * self.num_odd[2]) as usize
}
fn index(&self, c: [i32; 3]) -> Option<u32> {
if (0..3).any(|i| c[i] < 0 || c[i] > self.dims[i]) {
return None;
}
let parity = c[0].rem_euclid(2);
if c[1].rem_euclid(2) != parity || c[2].rem_euclid(2) != parity {
return None;
}
let (base, counts) = if parity == 0 {
(0, self.num_even)
} else {
(self.corners_len as i32, self.num_odd)
};
let [i, j, k] = [c[0] / 2, c[1] / 2, c[2] / 2];
Some((base + i + (j + k * counts[1]) * counts[0]) as u32)
}
fn point(&self, c: [i32; 3]) -> Vector {
self.origin + Vector::new(c[0] as Real, c[1] as Real, c[2] as Real) * self.half_cell
}
fn coords(&self) -> Vec<[i32; 3]> {
let mut coords = Vec::with_capacity(self.len());
for (parity, counts) in [(0, self.num_even), (1, self.num_odd)] {
for k in 0..counts[2] {
for j in 0..counts[1] {
for i in 0..counts[0] {
coords.push([i * 2 + parity, j * 2 + parity, k * 2 + parity]);
}
}
}
}
coords
}
}
fn lattice_tetrahedra(lattice: &Lattice, coords: &[[i32; 3]]) -> Vec<[u32; 4]> {
let mut tets = Vec::new();
for (center, &c) in coords.iter().enumerate().skip(lattice.corners_len) {
let center = center as u32;
for axis in 0..3 {
let mut step = [0; 3];
step[axis] = 2;
let Some(opposite) = lattice.index(core::array::from_fn(|i| c[i] + step[i])) else {
continue;
};
let (u, v) = ((axis + 1) % 3, (axis + 2) % 3);
let mut corners = [0; 4];
let mut complete = true;
for (k, [su, sv]) in [[1, 1], [-1, 1], [-1, -1], [1, -1]].into_iter().enumerate() {
let mut corner = c;
corner[axis] += 1;
corner[u] += su;
corner[v] += sv;
match lattice.index(corner) {
Some(corner) => corners[k] = corner,
None => {
complete = false;
break;
}
}
}
if !complete {
continue;
}
for k in 0..4 {
tets.push([center, opposite, corners[k], corners[(k + 1) % 4]]);
}
}
}
tets
}
fn is_closed(indices: &[[u32; 3]]) -> bool {
let mut edges: HashMap<[u32; 2], u32> = HashMap::default();
for tri in indices {
for k in 0..3 {
*edges.entry(edge_key(tri[k], tri[(k + 1) % 3])).or_insert(0) += 1;
}
}
edges.values().all(|count| *count == 2)
}
fn signed_distance(mesh: &TriMesh, pt: Vector) -> Real {
let (proj, _) = mesh.project_local_point_and_get_location(pt, false);
let dist = (pt - proj.point).length();
if proj.is_inside {
dist
} else {
-dist
}
}
fn edge_key(a: u32, b: u32) -> [u32; 2] {
if a < b {
[a, b]
} else {
[b, a]
}
}
fn orient_tet(pts: &[Vector], mut tet: [u32; 4], min_volume: Real) -> Option<[u32; 4]> {
let [a, b, c, d] = tet.map(|i| pts[i as usize]);
let volume = (b - a).cross(c - a).dot(d - a) / 6.0;
if volume.abs() < min_volume {
return None;
}
if volume < 0.0 {
tet.swap(0, 1);
}
Some(tet)
}
pub(super) struct BackgroundGrid {
pub points: Vec<Vector>,
pub cells: Vec<[u32; 4]>,
}
pub(super) fn domain(aabb: Aabb, cell_size: Real) -> Aabb {
aabb.loosened(cell_size)
}
pub(super) fn uniform_grid(aabb: Aabb, cell_size: Real) -> BackgroundGrid {
let lattice = Lattice::covering(aabb, cell_size);
let coords = lattice.coords();
let points = coords.iter().map(|&c| lattice.point(c)).collect();
let cells = lattice_tetrahedra(&lattice, &coords);
BackgroundGrid { points, cells }
}
fn flood_fill_inside(
oracle: &MeshOracle,
points: &[Vector],
grid_cells: &[[u32; 4]],
crossing: &[bool],
) -> Vec<bool> {
let mut parent: Vec<u32> = (0..grid_cells.len() as u32).collect();
fn root(parent: &mut [u32], mut i: u32) -> u32 {
while parent[i as usize] != i {
parent[i as usize] = parent[parent[i as usize] as usize];
i = parent[i as usize];
}
i
}
let mut last_at_vertex = alloc::vec![u32::MAX; points.len()];
for (id, cell) in grid_cells.iter().enumerate() {
if crossing[id] {
continue;
}
for v in cell {
let previous = last_at_vertex[*v as usize];
last_at_vertex[*v as usize] = id as u32;
if previous != u32::MAX {
let (a, b) = (root(&mut parent, previous), root(&mut parent, id as u32));
parent[a as usize] = b;
}
}
}
let mut inside_root: HashMap<u32, bool> = HashMap::default();
let mut inside = alloc::vec![false; grid_cells.len()];
for id in 0..grid_cells.len() {
if crossing[id] {
continue;
}
let component = root(&mut parent, id as u32);
let is_inside = *inside_root.entry(component).or_insert_with(|| {
let centroid = grid_cells[component as usize]
.iter()
.map(|v| points[*v as usize])
.sum::<Vector>()
/ 4.0;
oracle.signed_distance(centroid) > 0.0
});
inside[id] = is_inside;
}
inside
}
fn cover_grid(
grid: BackgroundGrid,
oracle: &MeshOracle,
params: &VolumeMeshParameters,
min_volume: Real,
) -> Option<VolumeMesh> {
let BackgroundGrid {
points,
cells: grid_cells,
} = grid;
let crossing: Vec<bool> = par_map(&grid_cells, |tet| {
let pts = tet.map(|v| points[v as usize]);
oracle.surface_intersects(&pts)
});
let keep: Vec<bool> = if params.enclosure == MeshEnclosure::Crust {
crossing
} else {
let inside = flood_fill_inside(oracle, &points, &grid_cells, &crossing);
crossing
.iter()
.zip(&inside)
.map(|(crossing, inside)| *crossing || *inside)
.collect()
};
let ids: Vec<u32> = (0..grid_cells.len() as u32).collect();
let kept = par_map(&ids, |id| {
if keep[*id as usize] {
orient_tet(&points, grid_cells[*id as usize], min_volume)
} else {
None
}
});
let cells: Vec<[u32; 4]> = kept.into_iter().flatten().collect();
if cells.is_empty() {
return None;
}
let mut result = VolumeMesh {
vertices: points,
cells,
};
result.compact();
Some(result)
}
pub fn tetrahedralize(
vertices: &[Vector],
indices: &[[u32; 3]],
params: &VolumeMeshParameters,
) -> Option<VolumeMesh> {
let cell_size = params.cell_size;
if vertices.is_empty() || indices.is_empty() || cell_size <= 0.0 || cell_size.is_nan() {
return None;
}
let crust = params.enclosure == MeshEnclosure::Crust;
let flags = if crust {
TriMeshFlags::MERGE_DUPLICATE_VERTICES
} else {
TriMeshFlags::ORIENTED | TriMeshFlags::MERGE_DUPLICATE_VERTICES
};
let mesh = TriMesh::with_flags(vertices.to_vec(), indices.to_vec(), flags)
.ok()
.filter(|mesh| crust || (mesh.pseudo_normals().is_some() && is_closed(mesh.indices())))?;
let oracle = MeshOracle::new(&mesh);
let grid = if params.cover_subdivisions > 0 {
super::cover_octree::cover_octree_grid(
&oracle,
domain(mesh.local_aabb(), cell_size),
params,
)?
} else {
uniform_grid(domain(mesh.local_aabb(), cell_size), cell_size)
};
let mut result = cover_grid(
grid,
&oracle,
params,
cell_size * cell_size * cell_size * 1.0e-6,
)?;
super::cover_smoothing::smooth_cover(&mut result, &oracle, params);
Some(result)
}