dedup_mesh 0.2.0

Deduplicates vertices in a 3d mesh
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2025 lacklustr@protonmail.com https://github.com/eadf

use crate::{DeDupError, Scalar};
use vob::Vob;

#[derive(Clone, Debug)]
pub struct Aabb<T: Scalar> {
    pub min: [T; 3],
    pub max: [T; 3],
}
impl<T: Scalar> Aabb<T> {
    pub(crate) fn new_from_checked_data(
        input_vertices: &[impl Into<[T; 3]> + Clone + Sync],
        used_vertices: &Option<Vob>,
    ) -> Result<Aabb<T>, DeDupError> {
        let mut aabb = Aabb {
            min: [T::infinity(); 3],
            max: [T::neg_infinity(); 3],
        };

        let mut update_aabb = |v: [T; 3]| -> Result<(), DeDupError> {
            v.finite_check()?;
            aabb.extend(v);
            Ok(())
        };

        if let Some(used_vertices) = used_vertices {
            for i in used_vertices.iter_set_bits(..) {
                update_aabb(input_vertices.ᚦget(i).clone().into())?
            }
        } else {
            for v in input_vertices.iter() {
                update_aabb(v.clone().into())?
            }
        }
        Ok(aabb)
    }

    #[inline(always)]
    pub(crate) fn extend(&mut self, point: [T; 3]) {
        self.min[0] = self.min[0].min(point[0]);
        self.min[1] = self.min[1].min(point[1]);
        self.min[2] = self.min[2].min(point[2]);
        self.max[0] = self.max[0].max(point[0]);
        self.max[1] = self.max[1].max(point[1]);
        self.max[2] = self.max[2].max(point[2]);
    }

    #[inline(always)]
    pub(crate) fn center(&self, scale: T) -> [T; 3] {
        let inv_scale = T::ONE / scale;

        let cx = (((self.min[0] * scale) + (self.max[0] * scale)) / T::TWO) * inv_scale;
        let cy = (((self.min[1] * scale) + (self.max[1] * scale)) / T::TWO) * inv_scale;
        let cz = (((self.min[2] * scale) + (self.max[2] * scale)) / T::TWO) * inv_scale;

        [cx, cy, cz]
    }

    pub(crate) fn longest_axis_and_center(&self) -> (usize, [T; 3]) {
        // Find the maximum absolute value across all coordinates
        let mut max_val = self.min[0].abs().max(self.max[0].abs());
        max_val = max_val.max(self.min[1].abs().max(self.max[1].abs()));
        max_val = max_val.max(self.min[2].abs().max(self.max[2].abs()));

        // Choose a safe scaling factor (e.g., if above half of f64::MAX)
        let scale = if max_val > T::QUARTER_OF_MAX {
            T::ONE / T::FOUR // scale down aggressively
        } else {
            T::ONE // no scaling needed
        };

        // Apply scaling (only when needed)
        let size_x = (self.max[0] * scale) - (self.min[0] * scale);
        let size_y = (self.max[1] * scale) - (self.min[1] * scale);
        let size_z = (self.max[2] * scale) - (self.min[2] * scale);

        let axis = if size_x >= size_y && size_x >= size_z {
            0
        } else if size_y >= size_z {
            1
        } else {
            2
        };
        (axis, self.center(scale))
    }
}

pub trait Array3<T: Scalar> {
    //fn add(&self, other: [T; 3]) -> [T; 3];
    fn sub(&self, other: [T; 3]) -> [T; 3];

    fn get_hash(self) -> (T::Bits, T::Bits, T::Bits);
    fn finite_check(self) -> Result<(), DeDupError>;
}

impl<T: Scalar> Array3<T> for [T; 3] {
    /*#[inline(always)]
    fn add(&self, other: [T; 3]) -> [T; 3] {
        [self[0] + other[0], self[1] + other[1], self[2] + other[2]]
    }*/

    #[inline(always)]
    fn sub(&self, other: [T; 3]) -> [T; 3] {
        [self[0] - other[0], self[1] - other[1], self[2] - other[2]]
    }

    #[inline(always)]
    fn get_hash(self) -> (T::Bits, T::Bits, T::Bits) {
        // try to get rid of the -0.0 value by adding 0.0
        let x = self[0] + T::zero();
        let y = self[1] + T::zero();
        let z = self[2] + T::zero();
        (x.to_bits(), y.to_bits(), z.to_bits())
    }

    #[inline(always)]
    fn finite_check(self) -> Result<(), DeDupError> {
        if !(self[0].is_finite() && self[1].is_finite() && self[2].is_finite()) {
            return Err(DeDupError(
                format!("Non finite vertex coordinate found {self:?}").to_string(),
            ));
        }
        Ok(())
    }
}

pub(crate) trait UnsafeVob {
    // unchecked (thorn) get()
    fn ᚦget(&self, index: usize) -> bool;
    // unchecked (thorn) set()
    fn ᚦset(&mut self, bit: usize, flag: bool);
}

impl UnsafeVob for Vob {
    #[cfg(not(debug_assertions))]
    #[inline(always)]
    fn ᚦget(&self, bit: usize) -> bool {
        unsafe { self.get_unchecked(bit) }
    }

    #[cfg(debug_assertions)]
    #[inline(always)]
    fn ᚦget(&self, bit: usize) -> bool {
        self.get(bit).unwrap()
    }

    #[cfg(not(debug_assertions))]
    #[inline(always)]
    fn ᚦset(&mut self, bit: usize, flag: bool) {
        unsafe {
            let _ = self.set_unchecked(bit, flag);
        }
    }

    #[cfg(debug_assertions)]
    #[inline(always)]
    fn ᚦset(&mut self, bit: usize, flag: bool) {
        let _ = self.set(bit, flag);
    }
}

#[allow(dead_code)]
pub(crate) trait UnsafeArray<T> {
    // unchecked (thorn) get()
    fn ᚦget(&self, index: usize) -> &T;
    // unchecked (thorn) get_mut()
    //fn ᚦget_mut(&mut self, index: usize) -> &mut T;
}

impl<T> UnsafeArray<T> for [T] {
    #[cfg(debug_assertions)]
    #[inline(always)]
    fn ᚦget(&self, index: usize) -> &T {
        self.get(index).unwrap()
    }

    #[cfg(not(debug_assertions))]
    #[inline(always)]
    fn ᚦget(&self, index: usize) -> &T {
        unsafe { self.get_unchecked(index) }
    }

    /*
    #[cfg(debug_assertions)]
    #[inline(always)]
    fn ᚦget_mut(&mut self, index: usize) -> &mut T {
        self.get_mut(index).unwrap()
    }

    #[cfg(not(debug_assertions))]
    #[inline(always)]
    fn ᚦget_mut(&mut self, index: usize) -> &mut T {
        unsafe { self.get_unchecked_mut(index) }
    }
    */
}