use crate::data::{CellArray, Points, PolyData};
pub struct CleanParams {
pub tolerance: f64,
pub merge_points: bool,
pub remove_degenerate: bool,
}
impl Default for CleanParams {
fn default() -> Self {
Self {
tolerance: 1e-6,
merge_points: true,
remove_degenerate: true,
}
}
}
pub fn clean(input: &PolyData, params: &CleanParams) -> PolyData {
let (new_points, point_map) = if params.merge_points {
merge_points(&input.points, params.tolerance)
} else {
let map: Vec<usize> = (0..input.points.len()).collect();
let pts = input.points.clone();
(pts, map)
};
let mut output = PolyData::new();
output.points = new_points;
output.verts = remap_cells(&input.verts, &point_map, params.remove_degenerate, 1);
output.lines = remap_cells(&input.lines, &point_map, params.remove_degenerate, 2);
output.polys = remap_cells(&input.polys, &point_map, params.remove_degenerate, 3);
output.strips = remap_cells(&input.strips, &point_map, params.remove_degenerate, 3);
output
}
fn merge_points(points: &Points<f64>, tolerance: f64) -> (Points<f64>, Vec<usize>) {
let n = points.len();
if n == 0 {
return (Points::new(), vec![]);
}
let cell_size = if tolerance > 0.0 { tolerance } else { 1e-12 };
let inv_cell = 1.0 / cell_size;
let mut point_map = vec![0usize; n];
let pts = points.as_flat_slice();
let mut keyed: Vec<(u64, u32)> = Vec::with_capacity(n);
for i in 0..n {
let b = i * 3;
let gx = (pts[b] * inv_cell).floor() as i64;
let gy = (pts[b + 1] * inv_cell).floor() as i64;
let gz = (pts[b + 2] * inv_cell).floor() as i64;
keyed.push((morton_encode(gx, gy, gz), i as u32));
}
keyed.sort_unstable_by_key(|&(k, _)| k);
let mut new_points_flat: Vec<f64> = Vec::with_capacity(n * 3);
let mut current_key = u64::MAX;
let mut current_idx = 0usize;
for &(key, orig_idx) in &keyed {
if key != current_key {
current_key = key;
current_idx = new_points_flat.len() / 3;
let b = orig_idx as usize * 3;
new_points_flat.extend_from_slice(&pts[b..b + 3]);
}
point_map[orig_idx as usize] = current_idx;
}
(Points::from_flat_vec(new_points_flat), point_map)
}
#[inline]
fn morton_encode(x: i64, y: i64, z: i64) -> u64 {
let ux = x as u64;
let uy = y as u64;
let uz = z as u64;
let mut h = 0xcbf29ce484222325u64;
h ^= ux; h = h.wrapping_mul(0x100000001b3);
h ^= uy; h = h.wrapping_mul(0x100000001b3);
h ^= uz; h = h.wrapping_mul(0x100000001b3);
h
}
fn remap_cells(
cells: &CellArray,
point_map: &[usize],
remove_degenerate: bool,
min_unique: usize,
) -> CellArray {
let mut out = CellArray::new();
for cell in cells.iter() {
let remapped: Vec<i64> = cell
.iter()
.map(|&id| point_map[id as usize] as i64)
.collect();
if remove_degenerate {
let mut deduped: Vec<i64> = Vec::with_capacity(remapped.len());
for &id in &remapped {
if deduped.last() != Some(&id) {
deduped.push(id);
}
}
if deduped.len() > 1 && deduped.first() == deduped.last() {
deduped.pop();
}
if deduped.len() >= min_unique {
out.push_cell(&deduped);
}
} else {
out.push_cell(&remapped);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn merge_duplicate_points() {
let mut pd = PolyData::new();
pd.points = Points::from_vec(vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], ]);
pd.polys.push_cell(&[0, 1, 2]);
pd.polys.push_cell(&[3, 4, 5]);
let result = clean(&pd, &CleanParams::default());
assert_eq!(result.points.len(), 3);
assert_eq!(result.polys.num_cells(), 2);
assert_eq!(result.polys.cell(0), &[0, 1, 2]);
assert_eq!(result.polys.cell(1), &[0, 1, 2]);
}
#[test]
fn remove_degenerate_cell() {
let mut pd = PolyData::new();
pd.points = Points::from_vec(vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
]);
pd.polys.push_cell(&[0, 1, 0]);
let result = clean(&pd, &CleanParams::default());
assert_eq!(result.polys.num_cells(), 0); }
#[test]
fn no_merge_mode() {
let mut pd = PolyData::new();
pd.points = Points::from_vec(vec![
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0], ]);
pd.polys.push_cell(&[0, 1, 0]);
let result = clean(
&pd,
&CleanParams {
merge_points: false,
remove_degenerate: false,
..Default::default()
},
);
assert_eq!(result.points.len(), 2); assert_eq!(result.polys.num_cells(), 1); }
}