pub mod from_mesh;
pub mod winding;
#[cfg(test)]
mod tests;
use alloc::collections::BTreeSet;
use alloc::vec;
use alloc::vec::Vec;
use crate::real::Real;
use crate::shape::Shape3;
fn far<R: Real>() -> R {
R::from_f64(1e30)
}
fn transform_row<R: Real>(f: &[R], d: &mut [R], v: &mut [usize], z: &mut [R]) {
let n = f.len();
if n == 0 {
return;
}
let mut k = 0usize;
v[0] = 0;
z[0] = -far::<R>();
z[1] = far::<R>();
for q in 1..n {
let fq = f[q];
let qr = R::from_f64(q as f64);
loop {
let p = v[k];
let pr = R::from_f64(p as f64);
let s = ((fq + qr * qr) - (f[p] + pr * pr)) / ((qr - pr) + (qr - pr));
if s > z[k] {
k += 1;
v[k] = q;
z[k] = s;
z[k + 1] = far::<R>();
break;
}
if k == 0 {
v[0] = q;
z[0] = -far::<R>();
z[1] = far::<R>();
break;
}
k -= 1;
}
}
let mut k = 0usize;
for (q, slot) in d.iter_mut().enumerate() {
let qr = R::from_f64(q as f64);
while z[k + 1] < qr {
k += 1;
}
let p = v[k];
let pr = R::from_f64(p as f64);
*slot = (qr - pr) * (qr - pr) + f[p];
}
}
fn squared_edt<R: Real>(grid: &mut [R], size: [u32; 3]) {
let (nx, ny, nz) = (size[0] as usize, size[1] as usize, size[2] as usize);
let longest = nx.max(ny).max(nz);
let mut f = vec![R::ZERO; longest];
let mut d = vec![R::ZERO; longest];
let mut v = vec![0usize; longest];
let mut z = vec![R::ZERO; longest + 1];
let at = |x: usize, y: usize, z: usize| (z * ny + y) * nx + x;
for zi in 0..nz {
for yi in 0..ny {
for xi in 0..nx {
f[xi] = grid[at(xi, yi, zi)];
}
transform_row(&f[..nx], &mut d[..nx], &mut v[..nx], &mut z[..nx + 1]);
for xi in 0..nx {
grid[at(xi, yi, zi)] = d[xi];
}
}
}
for zi in 0..nz {
for xi in 0..nx {
for yi in 0..ny {
f[yi] = grid[at(xi, yi, zi)];
}
transform_row(&f[..ny], &mut d[..ny], &mut v[..ny], &mut z[..ny + 1]);
for yi in 0..ny {
grid[at(xi, yi, zi)] = d[yi];
}
}
}
for yi in 0..ny {
for xi in 0..nx {
for zi in 0..nz {
f[zi] = grid[at(xi, yi, zi)];
}
transform_row(&f[..nz], &mut d[..nz], &mut v[..nz], &mut z[..nz + 1]);
for zi in 0..nz {
grid[at(xi, yi, zi)] = d[zi];
}
}
}
}
pub fn signed_distance_field<R: Real>(
samples: &[R],
shape: &impl Shape3,
cell_size: R,
) -> crate::Result<Vec<R>> {
let size = shape.size();
if size[0] < 2 || size[1] < 2 || size[2] < 2 {
return Err(crate::Error::GridTooSmall { size });
}
let count = shape.element_count();
if samples.len() != count {
return Err(crate::Error::ShapeOverflow {
size,
product: samples.len() as u64,
});
}
let mut inside = vec![far::<R>(); count];
let mut outside = vec![far::<R>(); count];
for (i, &s) in samples.iter().enumerate() {
if s < R::ZERO {
inside[i] = R::ZERO;
} else {
outside[i] = R::ZERO;
}
}
squared_edt(&mut inside, size);
squared_edt(&mut outside, size);
let mut out = vec![R::ZERO; count];
for (i, slot) in out.iter_mut().enumerate() {
let d = if samples[i] < R::ZERO {
-outside[i].sqrt()
} else {
inside[i].sqrt()
};
*slot = d * cell_size;
}
Ok(out)
}
fn godunov<R: Real>(neighbours: [Option<R>; 3], h: R) -> Option<R> {
let mut a: [R; 3] = [far(), far(), far()];
let mut n = 0usize;
for v in neighbours.into_iter().flatten() {
a[n] = v;
n += 1;
}
if n == 0 {
return None;
}
a[..n].sort_by(|p, q| p.partial_cmp(q).unwrap_or(core::cmp::Ordering::Equal));
let mut candidate = a[0] + h;
if n > 1 && candidate > a[1] {
let (s, q) = (a[0] + a[1], a[0] * a[1]);
let disc = s * s - (R::ONE + R::ONE) * (q + q - h * h);
if disc >= R::ZERO {
candidate = (s + disc.sqrt()) * R::HALF;
}
if n > 2 && candidate > a[2] {
let s3 = a[0] + a[1] + a[2];
let q3 = a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
let three = R::from_f64(3.0);
let disc3 = s3 * s3 - three * (q3 - h * h);
if disc3 >= R::ZERO {
candidate = (s3 + disc3.sqrt()) / three;
}
}
}
Some(candidate)
}
fn sweep<R: Real>(d: &mut [R], frozen: &[bool], size: [u32; 3], h: R) {
let (nx, ny, nz) = (size[0] as usize, size[1] as usize, size[2] as usize);
let at = |x: usize, y: usize, z: usize| (z * ny + y) * nx + x;
for pass in 0..8u8 {
let (rx, ry, rz) = (pass & 1 != 0, pass & 2 != 0, pass & 4 != 0);
for zi in 0..nz {
let z = if rz { nz - 1 - zi } else { zi };
for yi in 0..ny {
let y = if ry { ny - 1 - yi } else { yi };
for xi in 0..nx {
let x = if rx { nx - 1 - xi } else { xi };
let i = at(x, y, z);
if frozen[i] {
continue;
}
let pick = |a: Option<R>, b: Option<R>| match (a, b) {
(Some(p), Some(q)) => Some(if p < q { p } else { q }),
(Some(p), None) | (None, Some(p)) => Some(p),
(None, None) => None,
};
let ax = pick(
(x > 0).then(|| d[at(x - 1, y, z)]),
(x + 1 < nx).then(|| d[at(x + 1, y, z)]),
);
let ay = pick(
(y > 0).then(|| d[at(x, y - 1, z)]),
(y + 1 < ny).then(|| d[at(x, y + 1, z)]),
);
let az = pick(
(z > 0).then(|| d[at(x, y, z - 1)]),
(z + 1 < nz).then(|| d[at(x, y, z + 1)]),
);
let Some(candidate) = godunov([ax, ay, az], h) else {
continue;
};
if candidate < d[i] {
d[i] = candidate;
}
}
}
}
}
}
pub fn signed_distance_field_swept<R: Real>(
samples: &[R],
shape: &impl Shape3,
cell_size: R,
) -> crate::Result<Vec<R>> {
let size = shape.size();
if size[0] < 2 || size[1] < 2 || size[2] < 2 {
return Err(crate::Error::GridTooSmall { size });
}
let count = shape.element_count();
if samples.len() != count {
return Err(crate::Error::ShapeOverflow {
size,
product: samples.len() as u64,
});
}
let (nx, ny, nz) = (size[0] as usize, size[1] as usize, size[2] as usize);
let at = |x: usize, y: usize, z: usize| (z * ny + y) * nx + x;
let mut d = vec![far::<R>(); count];
let mut frozen = vec![false; count];
for z in 0..nz {
for y in 0..ny {
for x in 0..nx {
let i = at(x, y, z);
let here = samples[i];
let inside = here < R::ZERO;
let mut best = far::<R>();
let mut neighbour = |j: usize| {
let there = samples[j];
if (there < R::ZERO) == inside {
return;
}
let denom = here - there;
if denom == R::ZERO {
return;
}
let t = (here / denom).abs();
if t < best {
best = t;
}
};
if x > 0 {
neighbour(at(x - 1, y, z));
}
if x + 1 < nx {
neighbour(at(x + 1, y, z));
}
if y > 0 {
neighbour(at(x, y - 1, z));
}
if y + 1 < ny {
neighbour(at(x, y + 1, z));
}
if z > 0 {
neighbour(at(x, y, z - 1));
}
if z + 1 < nz {
neighbour(at(x, y, z + 1));
}
if best < far::<R>() {
d[i] = best * cell_size;
frozen[i] = true;
}
}
}
}
sweep(&mut d, &frozen, size, cell_size);
let mut out = vec![R::ZERO; count];
for (i, slot) in out.iter_mut().enumerate() {
*slot = if samples[i] < R::ZERO { -d[i] } else { d[i] };
}
Ok(out)
}
pub fn signed_distance_field_marched<R: Real>(
samples: &[R],
shape: &impl Shape3,
cell_size: R,
) -> crate::Result<Vec<R>> {
let march = march(samples, shape, cell_size, far::<R>())?;
let mut out = vec![R::ZERO; march.distance.len()];
for (i, slot) in out.iter_mut().enumerate() {
*slot = if samples[i] < R::ZERO {
-march.distance[i]
} else {
march.distance[i]
};
}
Ok(out)
}
struct March<R> {
distance: Vec<R>,
finalised: Vec<bool>,
visited: usize,
}
fn march<R: Real>(
samples: &[R],
shape: &impl Shape3,
cell_size: R,
limit: R,
) -> crate::Result<March<R>> {
let size = shape.size();
if size[0] < 2 || size[1] < 2 || size[2] < 2 {
return Err(crate::Error::GridTooSmall { size });
}
let count = shape.element_count();
if samples.len() != count {
return Err(crate::Error::ShapeOverflow {
size,
product: samples.len() as u64,
});
}
let (nx, ny, nz) = (size[0] as usize, size[1] as usize, size[2] as usize);
let at = |x: usize, y: usize, z: usize| (z * ny + y) * nx + x;
let mut d = vec![far::<R>(); count];
let mut done = vec![false; count];
let mut visited = 0usize;
for z in 0..nz {
for y in 0..ny {
for x in 0..nx {
let i = at(x, y, z);
let here = samples[i];
let inside = here < R::ZERO;
let mut best = far::<R>();
let mut neighbour = |j: usize| {
let there = samples[j];
if (there < R::ZERO) == inside {
return;
}
let denom = here - there;
if denom != R::ZERO {
let t = (here / denom).abs();
if t < best {
best = t;
}
}
};
if x > 0 {
neighbour(at(x - 1, y, z));
}
if x + 1 < nx {
neighbour(at(x + 1, y, z));
}
if y > 0 {
neighbour(at(x, y - 1, z));
}
if y + 1 < ny {
neighbour(at(x, y + 1, z));
}
if z > 0 {
neighbour(at(x, y, z - 1));
}
if z + 1 < nz {
neighbour(at(x, y, z + 1));
}
if best < far::<R>() {
d[i] = best * cell_size;
done[i] = true;
visited += 1;
}
}
}
}
fn key<R: Real>(v: R) -> u64 {
v.as_f64().to_bits()
}
let mut queue: BTreeSet<(u64, usize)> = BTreeSet::new();
let mut tentative = vec![far::<R>(); count];
let neighbours_of = |i: usize| {
let x = i % nx;
let y = (i / nx) % ny;
let z = i / (nx * ny);
let mut out = [usize::MAX; 6];
let mut n = 0;
let push = |j: usize, out: &mut [usize; 6], n: &mut usize| {
out[*n] = j;
*n += 1;
};
if x > 0 {
push(at(x - 1, y, z), &mut out, &mut n);
}
if x + 1 < nx {
push(at(x + 1, y, z), &mut out, &mut n);
}
if y > 0 {
push(at(x, y - 1, z), &mut out, &mut n);
}
if y + 1 < ny {
push(at(x, y + 1, z), &mut out, &mut n);
}
if z > 0 {
push(at(x, y, z - 1), &mut out, &mut n);
}
if z + 1 < nz {
push(at(x, y, z + 1), &mut out, &mut n);
}
(out, n)
};
let update = |d: &[R], i: usize| -> Option<R> {
let x = i % nx;
let y = (i / nx) % ny;
let z = i / (nx * ny);
let axis = |lo: Option<usize>, hi: Option<usize>| -> Option<R> {
let pick = |j: Option<usize>| j.map(|j| d[j]).filter(|v| *v < far::<R>());
match (pick(lo), pick(hi)) {
(Some(p), Some(q)) => Some(if p < q { p } else { q }),
(Some(p), None) | (None, Some(p)) => Some(p),
(None, None) => None,
}
};
godunov(
[
axis(
(x > 0).then(|| at(x - 1, y, z)),
(x + 1 < nx).then(|| at(x + 1, y, z)),
),
axis(
(y > 0).then(|| at(x, y - 1, z)),
(y + 1 < ny).then(|| at(x, y + 1, z)),
),
axis(
(z > 0).then(|| at(x, y, z - 1)),
(z + 1 < nz).then(|| at(x, y, z + 1)),
),
],
cell_size,
)
};
for i in 0..count {
if !done[i] {
continue;
}
let (ns, n) = neighbours_of(i);
for &j in &ns[..n] {
if done[j] {
continue;
}
if let Some(v) = update(&d, j)
&& v < tentative[j]
{
queue.remove(&(key(tentative[j]), j));
tentative[j] = v;
queue.insert((key(v), j));
}
}
}
while let Some(&(k, i)) = queue.iter().next() {
queue.remove(&(k, i));
if done[i] {
continue;
}
if tentative[i] > limit {
break;
}
d[i] = tentative[i];
done[i] = true;
visited += 1;
let (ns, n) = neighbours_of(i);
for &j in &ns[..n] {
if done[j] {
continue;
}
if let Some(v) = update(&d, j)
&& v < tentative[j]
{
queue.remove(&(key(tentative[j]), j));
tentative[j] = v;
queue.insert((key(v), j));
}
}
}
Ok(March {
distance: d,
finalised: done,
visited,
})
}
pub fn reinitialise_narrow_band<R: Real>(
values: &[R],
shape: &impl Shape3,
cell_size: R,
band: u32,
) -> crate::Result<(Vec<R>, usize)> {
let reach = cell_size * R::from_f64(f64::from(band));
let solved = march(values, shape, cell_size, reach)?;
let size = shape.size();
let (nx, ny, nz) = (size[0] as usize, size[1] as usize, size[2] as usize);
let at = |x: usize, y: usize, z: usize| (z * ny + y) * nx + x;
let mut on_surface = alloc::vec![false; values.len()];
for z in 0..nz {
for y in 0..ny {
for x in 0..nx {
let i = at(x, y, z);
let inside = values[i] < R::ZERO;
let mut touching = |j: usize| {
if (values[j] < R::ZERO) != inside {
on_surface[i] = true;
}
};
if x > 0 {
touching(at(x - 1, y, z));
}
if x + 1 < nx {
touching(at(x + 1, y, z));
}
if y > 0 {
touching(at(x, y - 1, z));
}
if y + 1 < ny {
touching(at(x, y + 1, z));
}
if z > 0 {
touching(at(x, y, z - 1));
}
if z + 1 < nz {
touching(at(x, y, z + 1));
}
}
}
}
let mut out = alloc::vec::Vec::with_capacity(values.len());
for (i, &v) in values.iter().enumerate() {
if on_surface[i] {
out.push(v);
} else if solved.finalised[i] {
let d = solved.distance[i];
out.push(if v < R::ZERO { -d } else { d });
} else if v < R::ZERO {
out.push(if v > -reach { -reach } else { v });
} else {
out.push(if v < reach { reach } else { v });
}
}
Ok((out, solved.visited))
}
#[derive(Clone, Copy, Debug)]
pub struct SampledField<'a, R: Real, S: Shape3> {
values: &'a [R],
shape: &'a S,
origin: [R; 3],
cell_size: R,
}
impl<'a, R: Real, S: Shape3> SampledField<'a, R, S> {
pub fn new(values: &'a [R], shape: &'a S, origin: [R; 3], cell_size: R) -> crate::Result<Self> {
let size = shape.size();
if size[0] < 2 || size[1] < 2 || size[2] < 2 {
return Err(crate::Error::GridTooSmall { size });
}
if values.len() != shape.element_count() {
return Err(crate::Error::ShapeOverflow {
size,
product: values.len() as u64,
});
}
Ok(Self {
values,
shape,
origin,
cell_size,
})
}
}
impl<R: Real, S: Shape3> crate::Sdf for SampledField<'_, R, S> {
type Scalar = R;
fn sample(&self, p: [R; 3]) -> R {
let size = self.shape.size();
let (nx, ny, nz) = (size[0] as usize, size[1] as usize, size[2] as usize);
let mut base = [0usize; 3];
let mut frac = [R::ZERO; 3];
let limit = [nx - 2, ny - 2, nz - 2];
for axis in 0..3 {
let t = (p[axis] - self.origin[axis]) / self.cell_size;
let floor = t.floor();
let i = floor.as_f64();
let clamped = if i < 0.0 {
0
} else if i > limit[axis] as f64 {
limit[axis]
} else {
i as usize
};
base[axis] = clamped;
frac[axis] = t - R::from_f64(clamped as f64);
}
let at = |dx: usize, dy: usize, dz: usize| {
let x = base[0] + dx;
let y = base[1] + dy;
let z = base[2] + dz;
self.values[(z * ny + y) * nx + x]
};
let lerp = |a: R, b: R, t: R| a + (b - a) * t;
let x0 = lerp(at(0, 0, 0), at(1, 0, 0), frac[0]);
let x1 = lerp(at(0, 1, 0), at(1, 1, 0), frac[0]);
let x2 = lerp(at(0, 0, 1), at(1, 0, 1), frac[0]);
let x3 = lerp(at(0, 1, 1), at(1, 1, 1), frac[0]);
let y0 = lerp(x0, x1, frac[1]);
let y1 = lerp(x2, x3, frac[1]);
lerp(y0, y1, frac[2])
}
}