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 dedup_mesh::prelude::*;
use num_traits::AsPrimitive;
use rand::prelude::StdRng;
use rand::{Rng, SeedableRng};

#[allow(dead_code)]
fn generate_random_vertices<T: dedup_mesh::Scalar>(rng: &mut StdRng, count: usize) -> Vec<[T; 3]>
where
    f64: AsPrimitive<T>,
    f32: AsPrimitive<T>,
{
    (0..count)
        .map(|_| {
            [
                rng.random_range(-100.0..100.0).as_(),
                rng.random_range(-100.0..100.0).as_(),
                rng.random_range(-100.0..100.0).as_(),
            ]
        })
        .collect()
}

#[allow(dead_code)]
fn generate_clustered_vertices<T: dedup_mesh::Scalar>(
    rng: &mut StdRng,
    base_vertices: &[[T; 3]],
    count: usize,
) -> Vec<[T; 3]>
where
    f64: AsPrimitive<T>,
    f32: AsPrimitive<T>,
{
    (0..count)
        .map(|_| {
            if base_vertices.is_empty() {
                return [
                    rng.random_range(-100.0..100.0).as_(),
                    rng.random_range(-100.0..100.0).as_(),
                    rng.random_range(-100.0..100.0).as_(),
                ];
            }

            let base_idx = rng.random_range(0..base_vertices.len());
            let base = base_vertices[base_idx];

            // Generate [f32;3] close to the base [T;3]
            let offset_range = 0.01; // Small offset to create near-duplicates
            [
                base[0] + rng.random_range(-offset_range..offset_range).as_(),
                base[1] + rng.random_range(-offset_range..offset_range).as_(),
                base[2] + rng.random_range(-offset_range..offset_range).as_(),
            ]
        })
        .collect()
}

#[allow(dead_code)]
pub(crate) fn generate_test_data<T: dedup_mesh::Scalar>(vertex_count: usize) -> Vec<[T; 3]>
where
    f64: AsPrimitive<T>,
    f32: AsPrimitive<T>,
{
    let mut rng = StdRng::seed_from_u64(42); // Deterministic seed

    // Generate initial random vertices (30% of total)
    let initial_count = vertex_count * 3 / 10;
    let mut vertices: Vec<[T; 3]> = generate_random_vertices::<T>(&mut rng, initial_count);

    // Generate clustered vertices that create near-duplicates (70% of total)
    let clustered_count = vertex_count - initial_count;
    let clustered = generate_clustered_vertices::<T>(&mut rng, &vertices, clustered_count);
    vertices.extend(clustered);

    // Shuffle to mix random and clustered vertices
    use rand::seq::SliceRandom;
    vertices.shuffle(&mut rng);

    vertices
}

#[allow(dead_code)]
fn test1() {
    #[allow(unused_imports)]
    use dedup_mesh::prelude::*;
    let vertices: Vec<[f32; 3]> = vec![
        [0.0, 0.0, 0.0],
        [0.001, 0.0, 0.0], // Within tolerance
        [0.001, 0.50, 0.0],
        [0.001, 0.5001, 0.0], // Within tolerance
        [1.0, 0.0, 0.0],
        [1.001, 0.0, 0.0], // Within tolerance
    ];
    let indices: Vec<usize> = vec![3, 5, 1, 4, 0];
    let (deduplicated, indices) = dedup::<f32, usize, [f32; 3], MultiThreaded, PointCloud>(
        &vertices,
        &indices,
        0.01,
        PruneUnused,
        PruneDegenerate,
        CheckTolerance,
    )
    .unwrap();

    println!("deduplicated:{deduplicated:?}");
    println!("indices:{indices:?}");

    //assert_eq!(deduplicated.len(), 2);
    //assert_eq!(indices.len(), 2);
    //assert_eq!(indices[0], 1);
    //assert_eq!(indices[1], 0);
}

#[allow(dead_code)]
fn test2() {
    #[allow(unused_imports)]
    let vertices = vec![
        [1.0, 2.0, 3.0],
        [1.0, 2.0, 3.0],       // Exact duplicate
        [1.0000001, 2.0, 3.0], // Near duplicate (within tolerance)
        [4.0, 5.0, 6.0],
    ];

    let indices: [usize; 0] = [];
    let tolerance = 0.001;

    let (deduplicated, indices) = dedup::<f32, usize, [f32; 3], MultiThreaded, PointCloud>(
        &vertices,
        &indices,
        tolerance,
        PruneUnused,
        PruneDegenerate,
        CheckTolerance,
    )
    .unwrap();
    assert_eq!(deduplicated.len(), 2); // Should have 2 unique vertices
    println!("deduplicated:{deduplicated:?}",);
    println!("indices:{indices:?}",);

    //assert_eq!(deduplicated.len(), 2);
    //assert_eq!(indices.len(), 2);
    //assert_eq!(indices[0], 1);
    //assert_eq!(indices[1], 0);
}

#[allow(dead_code)]
fn test_simple_deduplication() {
    let vertices: Vec<[f32; 3]> = vec![
        [0.0, 0.0, 0.0],
        [0.00, 0.0, 0.0], // Within tolerance
        [1.0, 0.0, 0.0],
        [1.00, 0.0, 0.0], // Within tolerance
    ];
    let indices: Vec<usize> = vec![3, 1, 2, 0];
    let (vertices, indices) =
        dedup_exact_from_iter::<f32, usize, [f32; 3], Triangulated, SkipCheckFinite, _, _>(
            indices,
            |i: usize| vertices[i],
            vertices.len(),
            PruneDegenerate,
        )
        .unwrap();

    assert_eq!(vertices.len(), 2);
    assert_eq!(indices.len(), 3);
    assert_eq!(indices[0], 0);
    assert_eq!(indices[1], 1);
}

#[allow(dead_code)]
#[cfg(feature = "parallel")]
fn test_dedup_multi_threaded_1() {
    let vertices = vec![
        [1.0, 2.0, 3.0],
        [1.0, 2.0, 3.0],       // Exact duplicate
        [1.0000001, 2.0, 3.0], // Near duplicate (within tolerance)
        [4.0, 5.0, 6.0],
    ];

    let indices: [usize; 0] = [];
    let tolerance = 0.001;

    let result = dedup::<f32, usize, [f32; 3], MultiThreaded, PointCloud>(
        &vertices,
        &indices,
        tolerance,
        KeepUnused,
        PruneDegenerate,
        CheckTolerance,
    )
    .unwrap();
    assert_eq!(result.0.len(), 2); // Should have 2 unique vertices
    assert_eq!(result.1.len(), 0); // Should have 0 indices
}

#[allow(dead_code)]
#[cfg(feature = "parallel")]
fn test_dedup_too_little_data_4() {
    let mut vertices: Vec<[f64; 3]> = vec![[1.0, 10000.0, 10.0]];
    vertices.push([2.0, 10000.0, 10.0]);
    vertices.push([3.0, 10000.0, 10.0]);
    let indices: Vec<usize> = vec![0];
    let tolerance = 0.001;

    let result = dedup::<f64, usize, [f64; 3], MultiThreaded, Edges>(
        &vertices,
        &indices,
        tolerance,
        KeepUnused,
        KeepDegenerate,
        CheckTolerance,
    )
    .unwrap();
    assert_eq!(result.0.len(), 3);
    assert_eq!(result.1.len(), 0);
}

#[allow(dead_code)]
fn test_dedup_multi_threaded_10() {
    let mut vertices = generate_test_data(10);
    vertices.push([0.0, 1000.0, 0.0]);
    vertices.push([0.0, 1000.0, 0.000]);
    let mut indices: Vec<usize> = (0..10).collect();
    indices.push(0);
    indices.push(1);
    indices.push(2);
    indices.push(3);
    let tolerance = 0.001;

    let result = dedup::<f64, usize, [f64; 3], MultiThreaded, Triangulated>(
        &vertices,
        &indices,
        tolerance,
        PruneUnused,
        KeepDegenerate,
        CheckTolerance,
    )
    .unwrap();

    assert_eq!(result.0.len(), 10);
    assert_eq!(result.1.len(), 12);
    assert_eq!(result.0[0][0], -18.826440703720515);
}

#[allow(dead_code)]
fn test_dedup_multi_threaded_14() {
    let mut vertices = generate_test_data::<f64>(1);
    vertices.push([0.0, 10000.0, 10.0]);
    vertices.push([0.0, 10000.001, 10.0]);
    vertices.push([0.0, 10000.0, 10.0]);
    vertices[0] = [0.0, 180143985094819.0, 0.0];
    vertices[1] = [0.0, 180143985094819.009, 0.0];
    vertices[2] = [0.0, 180143985094719.1, 0.0];
    vertices.push([0.0, 0.0, 10.0]);

    let mut indices: Vec<usize> = (0..2).collect();
    indices.push(0);
    indices.push(1);
    indices.push(2);
    indices.push(3);
    let tolerance = 0.01;

    let (vertices, indices) = dedup::<f64, usize, [f64; 3], MultiThreaded, Triangulated>(
        &vertices,
        &indices,
        tolerance,
        KeepUnused,
        KeepDegenerate,
        CheckTolerance,
    )
    .unwrap();
    assert_eq!(vertices.len(), 4);
    assert_eq!(indices.len(), 6);
    //println!("vertices:{vertices:?}");
}

#[allow(dead_code)]
fn test_dedup_multi_threaded_17() {
    let mut vertices = generate_test_data::<f32>(1);
    vertices.push([0.0, 0.0, 0.0]);
    vertices.push([0.0, 10000.001, 10.0]);
    vertices.push([0.0, -335543.0, 10.0]);
    vertices[0] = [0.0, 335544.0, 0.0];
    vertices[1] = [0.0, 335544.009, 0.0];
    vertices[2] = [0.0, 335544.1, 0.0];
    vertices.push([0.0, 0.0, 10.0]);

    let mut indices: Vec<usize> = (0..2).collect();
    indices.push(0);
    indices.push(1);
    indices.push(2);
    indices.push(3);
    let tolerance = 0.01;

    let (vertices, indices) = dedup::<f32, usize, [f32; 3], MultiThreaded, Triangulated>(
        &vertices,
        &indices,
        tolerance,
        KeepUnused,
        KeepDegenerate,
        CheckTolerance,
    )
    .unwrap();
    assert_eq!(vertices.len(), 4);
    assert_eq!(indices.len(), 6);
}

#[allow(dead_code)]
fn test_dedup_multi_threaded_6() {
    let mut vertices = generate_test_data(10);
    vertices.push([0.0, 1000.0, 0.0]);
    let tolerance = 0.001;

    let result = dedup::<f32, usize, [f32; 3], MultiThreaded, Triangulated>(
        &vertices,
        &[],
        tolerance,
        KeepUnused,
        KeepDegenerate,
        CheckTolerance,
    )
    .unwrap();

    assert_eq!(result.0.len(), 11);
    assert_eq!(result.1.len(), 0);
}
fn main() {
    test_dedup_multi_threaded_6()
}