#[cfg(test)]
mod tests;
use alloc::vec::Vec;
use crate::{Error, MeshBuffer, Real, Result};
pub(crate) fn quantise<R: Real>(scaled: R) -> i64 {
let f = scaled.floor();
if f.is_finite() { f.as_f32() as i64 } else { 0 }
}
pub(crate) struct Lattice<R: Real> {
anchor: [R; 3],
inv_epsilon: R,
}
impl<R: Real> Lattice<R> {
pub(crate) fn new(positions: &[[R; 3]], epsilon: R) -> Self {
let mut anchor = [R::INFINITY; 3];
for p in positions {
for (a, slot) in anchor.iter_mut().enumerate() {
if p[a].is_finite() && p[a] < *slot {
*slot = p[a];
}
}
}
for slot in &mut anchor {
if !slot.is_finite() {
*slot = R::ZERO;
}
}
Self {
anchor,
inv_epsilon: epsilon.recip(),
}
}
pub(crate) fn key_of(&self, p: [R; 3]) -> [i64; 3] {
[
quantise((p[0] - self.anchor[0]) * self.inv_epsilon),
quantise((p[1] - self.anchor[1]) * self.inv_epsilon),
quantise((p[2] - self.anchor[2]) * self.inv_epsilon),
]
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct WeldReport {
pub vertices_before: usize,
pub vertices_after: usize,
pub triangles_before: usize,
pub triangles_after: usize,
pub triangles_collapsed: usize,
}
impl WeldReport {
#[must_use]
pub const fn vertices_removed(&self) -> usize {
self.vertices_before - self.vertices_after
}
#[must_use]
pub const fn is_noop(&self) -> bool {
self.vertices_before == self.vertices_after && self.triangles_collapsed == 0
}
}
#[must_use]
pub fn epsilon_for<R: Real>(cell_size: R) -> R {
cell_size * R::from_f64(crate::validate::ValidateConfig::WELD_EPSILON_REL)
}
#[derive(Debug)]
pub struct Welder<R: Real> {
cells: Vec<([i64; 3], u32)>,
remap: Vec<u32>,
kept: Vec<bool>,
_scalar: core::marker::PhantomData<R>,
}
impl<R: Real> Welder<R> {
#[must_use]
pub const fn new() -> Self {
Self {
cells: Vec::new(),
remap: Vec::new(),
kept: Vec::new(),
_scalar: core::marker::PhantomData,
}
}
#[must_use]
pub fn remap(&self) -> &[u32] {
&self.remap
}
pub fn weld(&mut self, mesh: &mut MeshBuffer<R>, epsilon: R) -> Result<WeldReport> {
self.weld_split_by(mesh, epsilon, &[])
}
pub fn weld_split_by(
&mut self,
mesh: &mut MeshBuffer<R>,
epsilon: R,
keys: &[u64],
) -> Result<WeldReport> {
if !keys.is_empty() && keys.len() != mesh.positions.len() {
return Err(Error::WeldKeyLengthMismatch {
keys: keys.len() as u64,
vertices: mesh.positions.len() as u64,
});
}
if !epsilon.is_finite() || epsilon <= R::ZERO {
return Err(Error::InvalidWeldEpsilon {
value: f64::from(epsilon.as_f32()),
});
}
debug_assert_eq!(
mesh.normals.len(),
mesh.positions.len(),
"a MeshBuffer carries one normal per vertex"
);
let n = mesh.positions.len();
for (at, &i) in mesh.indices.iter().enumerate() {
if i as usize >= n {
return Err(Error::IndexOutOfRange {
at: at as u64,
index: i,
vertices: n as u64,
});
}
}
let mut report = WeldReport {
vertices_before: n,
vertices_after: n,
triangles_before: mesh.indices.len() / 3,
triangles_after: mesh.indices.len() / 3,
triangles_collapsed: 0,
};
if n == 0 {
return Ok(report);
}
let lattice = Lattice::new(&mesh.positions, epsilon);
self.cells.clear();
self.cells.reserve(n);
for (i, p) in mesh.positions.iter().enumerate() {
self.cells.push((lattice.key_of(*p), i as u32));
}
self.cells.sort_unstable();
self.remap.clear();
self.remap.resize(n, u32::MAX);
self.kept.clear();
self.kept.resize(n, false);
let eps_sq = epsilon * epsilon;
let mut next_output = 0u32;
for v in 0..n {
let p = mesh.positions[v];
let base = lattice.key_of(p);
let mut best: Option<u32> = None;
for dz in -1..=1i64 {
for dy in -1..=1i64 {
for dx in -1..=1i64 {
let key = [base[0] + dx, base[1] + dy, base[2] + dz];
let lo = self.cells.partition_point(|(k, _)| *k < key);
for &(k, u) in &self.cells[lo..] {
if k != key {
break;
}
if u as usize >= v || !self.kept[u as usize] {
continue;
}
if best.is_some_and(|b| u >= b) {
continue;
}
if !keys.is_empty() && keys.get(u as usize) != keys.get(v) {
continue;
}
let q = mesh.positions[u as usize];
let d = [p[0] - q[0], p[1] - q[1], p[2] - q[2]];
if d[0] * d[0] + d[1] * d[1] + d[2] * d[2] <= eps_sq {
best = Some(u);
}
}
}
}
}
match best {
Some(u) => self.remap[v] = self.remap[u as usize],
None => {
self.kept[v] = true;
self.remap[v] = next_output;
next_output += 1;
}
}
}
report.vertices_after = next_output as usize;
if report.vertices_after == n {
return Ok(report);
}
let mut w = 0usize;
for v in 0..n {
if self.kept[v] {
mesh.positions[w] = mesh.positions[v];
mesh.normals[w] = mesh.normals[v];
w += 1;
}
}
debug_assert_eq!(w, report.vertices_after);
mesh.positions.truncate(w);
mesh.normals.truncate(w);
let mut t = 0usize;
for tri in 0..report.triangles_before {
let a = self.remap[mesh.indices[tri * 3] as usize];
let b = self.remap[mesh.indices[tri * 3 + 1] as usize];
let c = self.remap[mesh.indices[tri * 3 + 2] as usize];
if a == b || b == c || a == c {
report.triangles_collapsed += 1;
continue;
}
mesh.indices[t * 3] = a;
mesh.indices[t * 3 + 1] = b;
mesh.indices[t * 3 + 2] = c;
t += 1;
}
mesh.indices.truncate(t * 3);
report.triangles_after = t;
Ok(report)
}
}
impl<R: Real> Default for Welder<R> {
fn default() -> Self {
Self::new()
}
}