use std::cell::RefCell;
use crate::cell::Cell;
use crate::Error;
const MAX_CELLS: i64 = 16_777_216;
const CERT_REL: f64 = 1.0e-8;
const CERT_ABS: f64 = 1.0e-12;
#[derive(Default)]
struct Recycled {
frac: Vec<[f64; 3]>,
folded: Vec<[f64; 3]>,
bin: Vec<[i32; 3]>,
offsets: Vec<usize>,
occupants: Vec<usize>,
counts: Vec<usize>,
cursor: Vec<usize>,
}
thread_local! {
#[allow(clippy::missing_const_for_thread_local)]
static MESH_POOL: RefCell<Option<Recycled>> = RefCell::new(None);
}
fn take_recycled() -> Recycled {
MESH_POOL.with(|slot| slot.borrow_mut().take().unwrap_or_default())
}
fn recycle(buf: Recycled) {
const MAX_KEEP: usize = 1 << 20;
if buf.frac.capacity() > MAX_KEEP || buf.offsets.capacity() > MAX_KEEP {
return;
}
MESH_POOL.with(|slot| {
let mut guard = slot.borrow_mut();
if guard.is_none() {
*guard = Some(buf);
}
});
}
pub(crate) struct Mesh {
pub nx: i32,
pub ny: i32,
pub nz: i32,
pub widths: [f64; 3],
pub frac: Vec<[f64; 3]>,
pub folded: Vec<[f64; 3]>,
pub bin: Vec<[i32; 3]>,
pub(crate) offsets: Vec<usize>,
pub(crate) occupants: Vec<usize>,
counts: Vec<usize>,
cursor: Vec<usize>,
}
impl Drop for Mesh {
fn drop(&mut self) {
recycle(Recycled {
frac: std::mem::take(&mut self.frac),
folded: std::mem::take(&mut self.folded),
bin: std::mem::take(&mut self.bin),
offsets: std::mem::take(&mut self.offsets),
occupants: std::mem::take(&mut self.occupants),
counts: std::mem::take(&mut self.counts),
cursor: std::mem::take(&mut self.cursor),
});
}
}
impl Mesh {
pub(crate) fn build(
xyz: &[[f64; 3]],
simbox: &Cell,
active: Option<&[usize]>,
edge: f64,
) -> Result<Self, Error> {
let n_active = active.map_or(xyz.len(), |list| list.len());
if let Some(list) = active {
Self::assemble(xyz, simbox, edge, n_active, list.iter().copied())
} else {
Self::assemble(xyz, simbox, edge, n_active, 0..xyz.len())
}
}
fn assemble<I>(
xyz: &[[f64; 3]],
simbox: &Cell,
edge: f64,
n_active: usize,
indices: I,
) -> Result<Self, Error>
where
I: Iterator<Item = usize> + Clone,
{
let widths = simbox.widths();
let nx = bins_1d(widths[0], edge)?;
let ny = bins_1d(widths[1], edge)?;
let nz = bins_1d(widths[2], edge)?;
let ncell = (i64::from(nx))
.checked_mul(i64::from(ny))
.and_then(|v| v.checked_mul(i64::from(nz)))
.filter(|&v| v > 0 && v <= MAX_CELLS)
.ok_or(Error::TooManyCells)? as usize;
let n = xyz.len();
let mut buf = take_recycled();
if buf.frac.len() != n {
buf.frac.resize(n, [0.0; 3]);
buf.folded.resize(n, [0.0; 3]);
buf.bin.resize(n, [0; 3]);
}
buf.counts.clear();
buf.counts.resize(ncell, 0);
for i in indices.clone() {
let s = simbox.fractional(xyz[i]);
let ix = bin_coord(s[0], nx);
let iy = bin_coord(s[1], ny);
let iz = bin_coord(s[2], nz);
buf.frac[i] = s;
buf.folded[i] = simbox.cartesian(s);
buf.bin[i] = [ix, iy, iz];
buf.counts[cell_index(ix, iy, iz, nx, ny, nz)] += 1;
}
buf.offsets.clear();
buf.offsets.resize(ncell + 1, 0);
for c in 0..ncell {
buf.offsets[c + 1] = buf.offsets[c] + buf.counts[c];
}
buf.cursor.clear();
buf.cursor.resize(ncell + 1, 0);
buf.cursor.copy_from_slice(&buf.offsets);
buf.occupants.resize(n_active, 0);
for i in indices {
let [ix, iy, iz] = buf.bin[i];
let c = cell_index(ix, iy, iz, nx, ny, nz);
let slot = buf.cursor[c];
buf.occupants[slot] = i;
buf.cursor[c] = slot + 1;
}
Ok(Self {
nx,
ny,
nz,
widths,
frac: buf.frac,
folded: buf.folded,
bin: buf.bin,
offsets: buf.offsets,
occupants: buf.occupants,
counts: buf.counts,
cursor: buf.cursor,
})
}
#[inline(always)]
pub(crate) fn locate(&self, ix: i32, iy: i32, iz: i32) -> (usize, i32, i32, i32) {
let (cx, na) = split_axis(ix, self.nx);
let (cy, nb) = split_axis(iy, self.ny);
let (cz, nc) = split_axis(iz, self.nz);
let cell = ((cz * self.ny + cy) * self.nx + cx) as usize;
(cell, na, nb, nc)
}
pub(crate) fn cell_of(&self, ix: i32, iy: i32, iz: i32) -> usize {
cell_index(ix, iy, iz, self.nx, self.ny, self.nz)
}
pub(crate) fn slots(&self, cell: usize) -> &[usize] {
let lo = self.offsets[cell];
let hi = self.offsets[cell + 1];
&self.occupants[lo..hi]
}
pub(crate) fn image_reach(&self) -> i32 {
self.nx.max(self.ny).max(self.nz) / 2 + 2
}
}
pub(crate) fn target_edge(simbox: &Cell, hint: Option<f64>, fallback: f64) -> f64 {
let mut edge = hint.unwrap_or(fallback);
if !edge.is_finite() || edge <= 0.0 {
edge = fallback;
}
let w = simbox.widths();
edge.min(w[0]).min(w[1]).min(w[2])
}
pub(crate) fn for_new_layer(prev: [i32; 3], reach: [i32; 3], mut visit: impl FnMut(i32, i32, i32)) {
let [px, py, pz] = prev;
let [rx, ry, rz] = reach;
for dz in -rz..=rz {
for dy in -ry..=ry {
for dx in -rx..=rx {
if dx.abs() <= px && dy.abs() <= py && dz.abs() <= pz {
continue;
}
visit(dx, dy, dz);
}
}
}
}
pub(crate) fn axis_gap(s: f64, bin: i32, reach: i32, n: i32, w: f64) -> f64 {
let nf = f64::from(n);
let plus = (f64::from(bin + reach + 1) / nf - s) * w;
let minus = (s - f64::from(bin - reach) / nf) * w;
plus.min(minus)
}
pub(crate) fn slab_dist2(s: [f64; 3], unfolded: [i32; 3], n: [i32; 3], w: [f64; 3]) -> f64 {
let mut gap = 0.0_f64;
for a in 0..3 {
let nf = f64::from(n[a]);
let lo = f64::from(unfolded[a]) / nf;
let hi = f64::from(unfolded[a] + 1) / nf;
let axis = if s[a] < lo {
(lo - s[a]) * w[a]
} else if s[a] > hi {
(s[a] - hi) * w[a]
} else {
0.0
};
gap = gap.max(axis);
}
let d = certify(gap);
d * d
}
fn bins_1d(width: f64, edge: f64) -> Result<i32, Error> {
let n = (width / edge).floor().max(1.0);
if !n.is_finite() || n > 1_000_000.0 {
return Err(Error::TooManyCells);
}
Ok(n as i32)
}
fn bin_coord(s: f64, n: i32) -> i32 {
let mut c = (s * f64::from(n)) as i32;
if c < 0 {
c = 0;
}
if c >= n {
c = n - 1;
}
c
}
#[inline(always)]
fn split_axis(i: i32, n: i32) -> (i32, i32) {
if i >= 0 && i < n {
(i, 0)
} else {
(i.rem_euclid(n), i.div_euclid(n))
}
}
fn cell_index(ix: i32, iy: i32, iz: i32, nx: i32, ny: i32, nz: i32) -> usize {
let (cx, _) = split_axis(ix, nx);
let (cy, _) = split_axis(iy, ny);
let (cz, _) = split_axis(iz, nz);
((cz * ny + cy) * nx + cx) as usize
}
pub(crate) fn certify(dist: f64) -> f64 {
if dist <= 0.0 || !dist.is_finite() {
return 0.0;
}
let d = dist - dist * CERT_REL - CERT_ABS;
if d > 0.0 {
d
} else {
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_layer_is_the_cube_then_one_face() {
let mut first = Vec::new();
for_new_layer([-1, -1, -1], [1, 1, 1], |dx, dy, dz| {
first.push((dx, dy, dz));
});
assert_eq!(first.len(), 27);
let mut grown = Vec::new();
for_new_layer([1, 1, 1], [1, 2, 1], |dx, dy, dz| {
grown.push((dx, dy, dz));
});
assert_eq!(grown.len(), 2 * 3 * 3);
assert!(grown.iter().all(|(_, y, _)| y.abs() == 2));
}
#[test]
fn frontier_is_at_least_the_min_cell_bound() {
let s = [0.30, 0.30, 0.30];
let bin = [2, 2, 2];
let n = [8, 8, 8];
let w = [10.0, 10.0, 10.0];
let h = w[0] / f64::from(n[0]);
for reach in 1..=3 {
let gap = axis_gap(s[0], bin[0], reach, n[0], w[0])
.min(axis_gap(s[1], bin[1], reach, n[1], w[1]))
.min(axis_gap(s[2], bin[2], reach, n[2], w[2]));
let bound = gap * gap;
let loose = (f64::from(reach) * h) * (f64::from(reach) * h);
assert!(
bound + 1e-9 >= loose * (1.0 - 2.0 * CERT_REL),
"reach {reach}: {bound} vs {loose}"
);
}
}
}