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 super::*;

#[test]
fn test_f64_basic_cases() {
    // Test f32 version - use smaller axis or larger tolerance to stay in i16 range
    // longest_axis / grid_size = 10.0 / (0.01 * 2) = 10.0 / 0.02 = 500 < i16::MAX
    let result = f64::optimize_grid_tolerance_and_type(10.0, 0.01, false);
    assert!(result.is_ok());
    let (tol, int_type) = result.unwrap();
    assert_eq!(tol, 0.01);
    assert_eq!(int_type, SignedPrimInt::I16);

    // Test a case that should use i32
    // longest_axis / grid_size = 100.0 / (0.001 * 2) = 100.0 / 0.002 = 50000 > i16::MAX
    let result = f64::optimize_grid_tolerance_and_type(100.0, 0.001, false);
    assert!(result.is_ok());
    let (tol, int_type) = result.unwrap();
    assert_eq!(tol, 0.001);
    assert_eq!(int_type, SignedPrimInt::I32);

    // Test a case that should use i64
    let tolerance = 0.0000001;
    let result = f64::optimize_grid_tolerance_and_type(1000.0, tolerance, false);
    assert!(result.is_ok());
    let (tol, int_type) = result.unwrap();
    assert_eq!(tol, tolerance);
    assert!(1000.0 / (tolerance * 2.0) > i32::MAX as f64);
    assert!(1000.0 / (tolerance * 2.0) < i64::MAX as f64);
    assert_eq!(int_type, SignedPrimInt::I64);
}

#[test]
fn test_f64_i16_boundary() {
    let tolerance = 1e-6;
    let grid_size = tolerance * 2.0;

    // Right at i16::MAX boundary
    let longest_axis = (i16::MAX as f64) * grid_size;
    let result = f64::optimize_grid_tolerance_and_type(longest_axis, tolerance, false);
    assert!(result.is_ok());
    let (_, int_type) = result.unwrap();
    println!("At i16::MAX boundary: {int_type:?}");

    // Just over i16::MAX boundary
    let longest_axis = (i16::MAX as f64 + 1.0) * grid_size;
    let result = f64::optimize_grid_tolerance_and_type(longest_axis, tolerance, false);
    assert!(result.is_ok());
    let (_, int_type) = result.unwrap();
    assert_eq!(int_type, SignedPrimInt::I32);
}

#[test]
fn test_f64_i32_boundary() {
    let tolerance = 1e-6;
    let grid_size = tolerance * 2.0;

    // Right at i32::MAX boundary
    let longest_axis = (i32::MAX as f64) * grid_size;
    let result = f64::optimize_grid_tolerance_and_type(longest_axis, tolerance, false);
    assert!(result.is_ok());
    let (_, int_type) = result.unwrap();
    println!("At i32::MAX boundary: {int_type:?}");

    // Just over i32::MAX boundary
    let longest_axis = (i32::MAX as f64 + 1.0) * grid_size;
    let result = f64::optimize_grid_tolerance_and_type(longest_axis, tolerance, false);
    assert!(result.is_ok());
    let (_, int_type) = result.unwrap();
    assert_eq!(int_type, SignedPrimInt::I64);
}

#[test]
fn test_f64_mantissa_precision_limits() {
    const MANTISSA_LIMIT: f64 = ((1_u64 << f64::MANTISSA_DIGITS) - 1) as f64;

    // Use a reasonable tolerance that will create meaningful max_safe_coord
    let tolerance = 1.0; // Much larger tolerance
    let grid_size = tolerance * 2.0;
    let max_safe_coord = MANTISSA_LIMIT * grid_size;

    println!("tMANTISSA_LIMIT: {MANTISSA_LIMIT}");
    println!("tMax safe coord: {max_safe_coord}");
    println!("tGrid size: {grid_size}");

    // Test right at the mantissa precision limit
    let result = f64::optimize_grid_tolerance_and_type(max_safe_coord * 0.99, tolerance, false);
    assert!(result.is_ok());
    println!("Just under limit: OK");

    // Test just over the mantissa precision limit without scaling
    let over_limit = max_safe_coord * 1.1;
    println!("Testing with axis: {over_limit}");
    let result = f64::optimize_grid_tolerance_and_type(over_limit, tolerance, false);
    assert!(
        result.is_err(),
        "Should fail without scaling when over mantissa limit"
    );
    println!("Error without scaling: {}", result.unwrap_err());
}

#[test]
fn test_f32_mantissa_precision_limits() {
    const MANTISSA_LIMIT: f32 = ((1_u64 << f32::MANTISSA_DIGITS) - 1) as f32;

    // Use a reasonable tolerance that will create meaningful max_safe_coord
    let tolerance = 1.0; // Much larger tolerance
    let grid_size = tolerance * 2.0;
    let max_safe_coord = MANTISSA_LIMIT * grid_size;

    println!("tMANTISSA_LIMIT: {MANTISSA_LIMIT}");
    println!("tMax safe coord: {max_safe_coord}");
    println!("tGrid size: {grid_size}");

    // Test right at the mantissa precision limit
    let result = f32::optimize_grid_tolerance_and_type(max_safe_coord * 0.99, tolerance, false);
    assert!(result.is_ok());
    println!("Just under limit: OK");

    // Test just over the mantissa precision limit without scaling
    let over_limit = max_safe_coord * 1.1;
    println!("Testing with axis: {over_limit}");
    let result = f32::optimize_grid_tolerance_and_type(over_limit, tolerance, false);
    assert!(
        result.is_err(),
        "Should fail without scaling when over mantissa limit"
    );
    println!("Error without scaling: {}", result.unwrap_err());
}

#[test]
fn test_f64_scaling_behavior() {
    const MANTISSA_LIMIT: f64 = ((1_u64 << f64::MANTISSA_DIGITS) - 1) as f64;
    let tolerance = 1e-3;
    let grid_size = tolerance * 2.0;
    let max_safe_coord = MANTISSA_LIMIT * grid_size;

    // Test scaling when longest_axis exceeds mantissa precision
    let large_axis = max_safe_coord * 10.0;
    let result = f64::optimize_grid_tolerance_and_type(large_axis, tolerance, true);
    assert!(result.is_ok());

    let (new_tolerance, int_type) = result.unwrap();
    println!("Original tolerance: {tolerance}, New tolerance: {new_tolerance}");
    println!("Integer type selected: {int_type:?}",);

    // Verify the new tolerance actually works
    let new_grid_size = new_tolerance * 2.0;
    let new_max_safe_coord = MANTISSA_LIMIT * new_grid_size;
    println!("New max safe coord: {new_max_safe_coord}, Large axis: {large_axis}");
    assert!(
        large_axis <= new_max_safe_coord,
        "Scaling should make axis fit within precision limits"
    );
}

#[test]
fn test_f64_extreme_scaling() {
    // Test with extremely large axis values
    let huge_axis = 1e20;
    let tiny_tolerance = 1e-15;

    let result = f64::optimize_grid_tolerance_and_type(huge_axis, tiny_tolerance, true);
    assert!(result.is_ok());

    let (new_tolerance, int_type) = result.unwrap();
    println!("Extreme case - Original tolerance: {tiny_tolerance}, New tolerance: {new_tolerance}");
    println!("Integer type: {int_type:?}",);

    // The scaled tolerance should be much larger
    assert!(new_tolerance > tiny_tolerance);
}

#[test]
fn test_f32_basic_cases() {
    // Test f32 version - use smaller axis or larger tolerance to stay in i16 range
    // longest_axis / grid_size = 10.0 / (0.01 * 2) = 10.0 / 0.02 = 500 < i16::MAX
    let result = f32::optimize_grid_tolerance_and_type(10.0, 0.01, false);
    assert!(result.is_ok());
    let (tol, int_type) = result.unwrap();
    assert_eq!(tol, 0.01);
    assert_eq!(int_type, SignedPrimInt::I16);

    // Test a case that should use i32
    // longest_axis / grid_size = 100.0 / (0.001 * 2) = 100.0 / 0.002 = 50000 > i16::MAX
    let result = f32::optimize_grid_tolerance_and_type(100.0, 0.001, false);
    assert!(result.is_ok());
    let (tol, int_type) = result.unwrap();
    assert_eq!(tol, 0.001);
    assert_eq!(int_type, SignedPrimInt::I32);
}

#[test]
fn test_f32_mantissa_limits() {
    const MANTISSA_LIMIT: f32 = ((1_u32 << 23) - 1) as f32;
    let tolerance = 1e-3f32;
    let grid_size = tolerance * 2.0;
    let max_safe_coord = MANTISSA_LIMIT * grid_size;

    println!("f32 MANTISSA_LIMIT: {MANTISSA_LIMIT}",);
    println!("f32 Max safe coord: {max_safe_coord}",);

    let result = f32::optimize_grid_tolerance_and_type(max_safe_coord * 0.9, tolerance, false);
    assert!(result.is_ok());
}

#[test]
fn test_potential_bug_cases() {
    // Test case that might expose the scaling bug
    let tolerance = 1e-10;
    let huge_axis = 1e15;

    let result = f64::optimize_grid_tolerance_and_type(huge_axis, tolerance, true);
    if let Ok((new_tolerance, int_type)) = result {
        println!("Bug test - New tolerance: {new_tolerance}, Int type: {int_type:?}");

        // Check if the scaling actually makes sense
        let new_grid_size = new_tolerance * 2.0;
        let grid_coordinate = huge_axis / new_grid_size;
        println!("Resulting grid coordinate: {grid_coordinate}");

        // This coordinate should fit in the selected integer type
        match int_type {
            SignedPrimInt::I16 => assert!(grid_coordinate.abs() < i16::MAX as f64),
            SignedPrimInt::I32 => assert!(grid_coordinate.abs() < i32::MAX as f64),
            SignedPrimInt::I64 => assert!(grid_coordinate.abs() < i64::MAX as f64),
        }
    } else {
        panic!("Scaling should have succeeded: {result:?}",);
    }
}

#[test]
fn test_precision_consistency() {
    // Test that the function maintains floating-point precision requirements
    let tolerance = 1e-12;
    let axis = 1e6;

    let result = f64::optimize_grid_tolerance_and_type(axis, tolerance, true);
    if let Ok((new_tolerance, _)) = result {
        let grid_size = new_tolerance * 2.0;

        // Find the grid coordinate and cell center
        let grid_coord = (axis / grid_size).floor();
        let cell_center = grid_coord * grid_size;

        // Test moving to the next cell center
        let next_cell_center = cell_center + grid_size;
        let next_grid_coord = next_cell_center / grid_size;

        println!("Grid coord: {grid_coord}, Next grid coord: {next_grid_coord}",);
        assert!(
            (next_grid_coord - grid_coord).abs() >= 1.0,
            "Adjacent grid cells should be distinguishable"
        );
    } else {
        panic!("Scaling should have succeeded: {result:?}",);
    }
}