use alloc::vec;
use alloc::vec::Vec;
use crate::Real;
use crate::vec3;
pub(crate) const MAX_CELLS_PER_TRIANGLE: usize = 512;
pub(crate) const MAX_TOTAL_CELLS: u64 = 1 << 22;
#[inline]
pub(crate) fn cell_of<R: Real>(scaled: R) -> i64 {
let f = scaled.floor();
if f.is_finite() { f.as_f32() as i64 } else { 0 }
}
pub(crate) fn point_triangle_distance_squared<R: Real>(
p: [R; 3],
a: [R; 3],
b: [R; 3],
c: [R; 3],
) -> R {
let ab = vec3::sub(b, a);
let ac = vec3::sub(c, a);
let ap = vec3::sub(p, a);
let d1 = vec3::dot(ab, ap);
let d2 = vec3::dot(ac, ap);
if d1 <= R::ZERO && d2 <= R::ZERO {
return vec3::length_squared(ap); }
let bp = vec3::sub(p, b);
let d3 = vec3::dot(ab, bp);
let d4 = vec3::dot(ac, bp);
if d3 >= R::ZERO && d4 <= d3 {
return vec3::length_squared(bp); }
let vc_ = d1 * d4 - d3 * d2;
if vc_ <= R::ZERO && d1 >= R::ZERO && d3 <= R::ZERO {
let v = d1 / (d1 - d3); return vec3::length_squared(vec3::sub(ap, vec3::scale(ab, v)));
}
let cp = vec3::sub(p, c);
let d5 = vec3::dot(ab, cp);
let d6 = vec3::dot(ac, cp);
if d6 >= R::ZERO && d5 <= d6 {
return vec3::length_squared(cp); }
let vb_ = d5 * d2 - d1 * d6;
if vb_ <= R::ZERO && d2 >= R::ZERO && d6 <= R::ZERO {
let w = d2 / (d2 - d6); return vec3::length_squared(vec3::sub(ap, vec3::scale(ac, w)));
}
let va_ = d3 * d6 - d5 * d4;
if va_ <= R::ZERO && (d4 - d3) >= R::ZERO && (d5 - d6) >= R::ZERO {
let w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); let bc = vec3::sub(c, b);
return vec3::length_squared(vec3::sub(bp, vec3::scale(bc, w)));
}
let denom = (va_ + vb_ + vc_).recip();
let v = vb_ * denom;
let w = vc_ * denom;
let closest = vec3::sub(vec3::sub(ap, vec3::scale(ab, v)), vec3::scale(ac, w));
vec3::length_squared(closest)
}
#[derive(Clone, Debug)]
pub(crate) struct TriangleGrid<R: Real> {
lo: [R; 3],
cell_size: R,
inv_cell: R,
dims: [u32; 3],
offsets: Vec<u32>,
items: Vec<u32>,
}
impl<R: Real> TriangleGrid<R> {
pub(crate) fn build(
positions: &[[R; 3]],
tris: &[[u32; 3]],
cell_size: f64,
) -> crate::Result<Self> {
if !cell_size.is_finite() || cell_size <= 0.0 {
return Err(crate::Error::InvalidCellSize { value: cell_size });
}
let h = R::from_f64(cell_size);
let inv_cell = h.recip();
if tris.is_empty() {
return Ok(Self {
lo: [R::ZERO; 3],
cell_size: h,
inv_cell,
dims: [1; 3],
offsets: vec![0, 0],
items: Vec::new(),
});
}
let mut lo = [R::INFINITY; 3];
let mut hi = [R::NEG_INFINITY; 3];
for t in tris {
for &i in t {
let v = positions[i as usize];
for a in 0..3 {
lo[a] = lo[a].min(v[a]);
hi[a] = hi[a].max(v[a]);
}
}
}
let mut dims = [1u32; 3];
let mut total: u64 = 1;
for a in 0..3 {
let span = cell_of((hi[a] - lo[a]) * inv_cell) + 1;
let d = span.clamp(1, i64::from(u32::MAX)) as u32;
dims[a] = d;
total = total.saturating_mul(u64::from(d));
}
if total > MAX_TOTAL_CELLS {
return Err(crate::Error::CellSizeMismatch {
triangle: u64::MAX,
cells: u128::from(total),
cell_size,
});
}
let cells = (dims[0] as usize) * (dims[1] as usize) * (dims[2] as usize);
let mut grid = Self {
lo,
cell_size: h,
inv_cell,
dims,
offsets: vec![0; cells + 1],
items: Vec::new(),
};
for (ti, t) in tris.iter().enumerate() {
let (blo, bhi) = grid.bounds_cells(positions, *t);
let span = span_cells(blo, bhi);
if span > MAX_CELLS_PER_TRIANGLE as u128 {
return Err(crate::Error::CellSizeMismatch {
triangle: ti as u64,
cells: span,
cell_size,
});
}
for z in blo[2]..=bhi[2] {
for y in blo[1]..=bhi[1] {
for x in blo[0]..=bhi[0] {
grid.offsets[grid_index(dims, x, y, z) + 1] += 1;
}
}
}
}
for c in 0..cells {
grid.offsets[c + 1] += grid.offsets[c];
}
let entries = grid.offsets[cells] as usize;
grid.items = vec![0; entries];
let mut cursor = grid.offsets.clone();
for (ti, t) in tris.iter().enumerate() {
let (blo, bhi) = grid.bounds_cells(positions, *t);
for z in blo[2]..=bhi[2] {
for y in blo[1]..=bhi[1] {
for x in blo[0]..=bhi[0] {
let c = grid_index(dims, x, y, z);
grid.items[cursor[c] as usize] = ti as u32;
cursor[c] += 1;
}
}
}
}
Ok(grid)
}
#[inline]
pub(crate) fn cell(&self, x: u32, y: u32, z: u32) -> &[u32] {
let c = grid_index(self.dims, x, y, z);
&self.items[self.offsets[c] as usize..self.offsets[c + 1] as usize]
}
pub(crate) fn nearest_distance_squared(
&self,
q: [R; 3],
positions: &[[R; 3]],
tris: &[[u32; 3]],
) -> R {
if self.items.is_empty() {
return R::INFINITY;
}
let c = [
self.axis_cell(q[0], 0),
self.axis_cell(q[1], 1),
self.axis_cell(q[2], 2),
];
let mut best = R::INFINITY;
let mut r: u32 = 0;
loop {
let blo = [
c[0].saturating_sub(r),
c[1].saturating_sub(r),
c[2].saturating_sub(r),
];
let bhi = [
(c[0].saturating_add(r)).min(self.dims[0] - 1),
(c[1].saturating_add(r)).min(self.dims[1] - 1),
(c[2].saturating_add(r)).min(self.dims[2] - 1),
];
for z in blo[2]..=bhi[2] {
for y in blo[1]..=bhi[1] {
for x in blo[0]..=bhi[0] {
if r > 0 && chebyshev(c, [x, y, z]) < r {
continue;
}
for &ti in self.cell(x, y, z) {
let t = tris[ti as usize];
let d = point_triangle_distance_squared(
q,
positions[t[0] as usize],
positions[t[1] as usize],
positions[t[2] as usize],
);
if d < best {
best = d;
}
}
}
}
}
let mut reach = R::INFINITY;
let mut bounded = false;
for a in 0..3 {
if blo[a] > 0 {
let wall = self.lo[a] + self.cell_size * R::from_f64(f64::from(blo[a]));
reach = reach.min(q[a] - wall);
bounded = true;
}
if bhi[a] < self.dims[a] - 1 {
let wall = self.lo[a] + self.cell_size * R::from_f64(f64::from(bhi[a] + 1));
reach = reach.min(wall - q[a]);
bounded = true;
}
}
if !bounded {
return best; }
let reach = reach.max(R::ZERO);
if best <= reach * reach {
return best;
}
r += 1;
}
}
#[inline]
fn axis_cell(&self, v: R, a: usize) -> u32 {
let i = cell_of((v - self.lo[a]) * self.inv_cell);
i.clamp(0, i64::from(self.dims[a] - 1)) as u32
}
fn bounds_cells(&self, positions: &[[R; 3]], t: [u32; 3]) -> ([u32; 3], [u32; 3]) {
let a = positions[t[0] as usize];
let b = positions[t[1] as usize];
let c = positions[t[2] as usize];
let mut lo = [0u32; 3];
let mut hi = [0u32; 3];
for ax in 0..3 {
lo[ax] = self.axis_cell(a[ax].min(b[ax]).min(c[ax]), ax);
hi[ax] = self.axis_cell(a[ax].max(b[ax]).max(c[ax]), ax);
}
(lo, hi)
}
}
#[inline]
fn span_cells(lo: [u32; 3], hi: [u32; 3]) -> u128 {
u128::from(hi[0] - lo[0] + 1) * u128::from(hi[1] - lo[1] + 1) * u128::from(hi[2] - lo[2] + 1)
}
#[inline]
fn grid_index(dims: [u32; 3], x: u32, y: u32, z: u32) -> usize {
(x as usize)
+ (y as usize) * (dims[0] as usize)
+ (z as usize) * (dims[0] as usize) * (dims[1] as usize)
}
#[inline]
fn chebyshev(a: [u32; 3], b: [u32; 3]) -> u32 {
a[0].abs_diff(b[0])
.max(a[1].abs_diff(b[1]))
.max(a[2].abs_diff(b[2]))
}