use oxigdal_algorithms::{
ThinPoint3, ThinningMethod, ThinningStats, thin_grid, thin_poisson_disk, thin_random,
thin_with_stats,
};
fn dist3(a: &ThinPoint3, b: &ThinPoint3) -> f64 {
let dx = a.x - b.x;
let dy = a.y - b.y;
let dz = a.z - b.z;
(dx * dx + dy * dy + dz * dz).sqrt()
}
fn unit_cube_points(n: usize, seed: u64) -> Vec<ThinPoint3> {
let mut state = seed.wrapping_add(1);
let mut step = || {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(state >> 11) as f64 / (1u64 << 53) as f64
};
(0..n)
.map(|_| ThinPoint3::new(step(), step(), step()))
.collect()
}
#[test]
fn test_thin_grid_empty_returns_empty() {
let pts: Vec<ThinPoint3> = Vec::new();
let out = thin_grid(&pts, 1.0);
assert!(out.is_empty());
}
#[test]
fn test_thin_grid_keeps_one_per_voxel() {
let pts = vec![
ThinPoint3::new(0.1, 0.1, 0.1),
ThinPoint3::new(0.2, 0.2, 0.2),
ThinPoint3::new(0.3, 0.3, 0.3),
ThinPoint3::new(0.4, 0.4, 0.4),
ThinPoint3::new(0.5, 0.5, 0.5),
];
let out = thin_grid(&pts, 1.0);
assert_eq!(out.len(), 1, "all five points share one voxel");
assert_eq!(out[0], pts[0]);
}
#[test]
fn test_thin_grid_large_cell_keeps_one_total() {
let pts = vec![
ThinPoint3::new(0.0, 0.0, 0.0),
ThinPoint3::new(1.0, 2.0, 3.0),
ThinPoint3::new(4.0, 5.0, 6.0),
ThinPoint3::new(7.0, 8.0, 9.0),
];
let out = thin_grid(&pts, 1e9);
assert_eq!(out.len(), 1, "huge cell ⇒ single voxel ⇒ single survivor");
}
#[test]
fn test_thin_grid_distinct_voxels_keeps_all() {
let pts = vec![
ThinPoint3::new(0.0, 0.0, 0.0),
ThinPoint3::new(10.0, 0.0, 0.0),
ThinPoint3::new(0.0, 10.0, 0.0),
ThinPoint3::new(0.0, 0.0, 10.0),
];
let out = thin_grid(&pts, 1.0);
assert_eq!(out.len(), 4, "four points in four distinct unit voxels");
}
#[test]
fn test_thin_random_target_count_respected() {
let pts: Vec<ThinPoint3> = (0..100)
.map(|i| ThinPoint3::new(i as f64, 0.0, 0.0))
.collect();
let out = thin_random(&pts, 10, 12345);
assert_eq!(out.len(), 10);
for p in &out {
assert!(pts.contains(p));
}
}
#[test]
fn test_thin_random_deterministic_same_seed() {
let pts: Vec<ThinPoint3> = (0..100)
.map(|i| ThinPoint3::new(i as f64, 0.0, 0.0))
.collect();
let a = thin_random(&pts, 20, 0xC0FFEE);
let b = thin_random(&pts, 20, 0xC0FFEE);
assert_eq!(a, b, "identical seed ⇒ identical output");
}
#[test]
fn test_thin_random_different_seed_different_result() {
let pts: Vec<ThinPoint3> = (0..100)
.map(|i| ThinPoint3::new(i as f64, 0.0, 0.0))
.collect();
let a = thin_random(&pts, 10, 1);
let b = thin_random(&pts, 10, 2);
assert_ne!(a, b);
}
#[test]
fn test_thin_random_target_geq_input_returns_all() {
let pts: Vec<ThinPoint3> = (0..100)
.map(|i| ThinPoint3::new(i as f64, 0.0, 0.0))
.collect();
let out = thin_random(&pts, 200, 42);
assert_eq!(out, pts);
}
#[test]
fn test_thin_random_zero_target_returns_empty() {
let pts: Vec<ThinPoint3> = (0..10)
.map(|i| ThinPoint3::new(i as f64, 0.0, 0.0))
.collect();
let out = thin_random(&pts, 0, 42);
assert!(out.is_empty());
}
#[test]
fn test_thin_poisson_disk_all_kept_pairs_at_least_min_distance() {
let pts = unit_cube_points(200, 1);
let min_distance = 0.2;
let out = thin_poisson_disk(&pts, min_distance, 0xBEEF);
let eps = 1e-12;
for (i, p) in out.iter().enumerate() {
for q in &out[i + 1..] {
assert!(
dist3(p, q) + eps >= min_distance,
"kept pair too close: dist={} < min={}",
dist3(p, q),
min_distance
);
}
}
}
#[test]
fn test_thin_poisson_disk_min_distance_zero_keeps_all() {
let pts = vec![
ThinPoint3::new(0.0, 0.0, 0.0),
ThinPoint3::new(0.0, 0.0, 0.0),
ThinPoint3::new(0.0, 0.0, 0.0),
];
let out = thin_poisson_disk(&pts, 0.0, 1);
assert_eq!(out, pts, "min_distance=0 disables the disk constraint");
}
#[test]
fn test_thin_poisson_disk_dense_input_thins_to_subset() {
let pts = unit_cube_points(100, 7);
let out = thin_poisson_disk(&pts, 0.3, 99);
assert!(!out.is_empty(), "at least one point survives");
assert!(out.len() < pts.len(), "dense input must be thinned");
}
#[test]
fn test_thin_poisson_disk_empty_returns_empty() {
let pts: Vec<ThinPoint3> = Vec::new();
let out = thin_poisson_disk(&pts, 1.0, 0);
assert!(out.is_empty());
}
#[test]
fn test_thin_poisson_disk_deterministic_same_seed() {
let pts = unit_cube_points(50, 3);
let a = thin_poisson_disk(&pts, 0.25, 0xABCD);
let b = thin_poisson_disk(&pts, 0.25, 0xABCD);
assert_eq!(a, b, "identical seed ⇒ identical output");
}
#[test]
fn test_thinning_stats_kept_count_correct() {
let pts: Vec<ThinPoint3> = (0..1000)
.map(|i| ThinPoint3::new(i as f64, 0.0, 0.0))
.collect();
let (out, stats) = thin_with_stats(
&pts,
ThinningMethod::Random {
target_count: 100,
seed: 11,
},
);
assert_eq!(stats.input_count, 1000);
assert_eq!(stats.kept_count, out.len());
assert_eq!(stats.kept_count, 100);
let expected = 1.0 - 100.0 / 1000.0;
assert!(
(stats.reduction_ratio - expected).abs() < 1e-12,
"reduction_ratio={} expected={}",
stats.reduction_ratio,
expected
);
}
#[test]
fn test_thinning_stats_zero_input_zero_ratio() {
let pts: Vec<ThinPoint3> = Vec::new();
let (_out, stats) = thin_with_stats(&pts, ThinningMethod::Grid { cell_size: 1.0 });
assert_eq!(stats.input_count, 0);
assert_eq!(stats.kept_count, 0);
assert_eq!(stats.reduction_ratio, 0.0);
}
#[test]
fn test_thinning_stats_constructor_matches_manual() {
let s = ThinningStats::new(200, 50);
assert_eq!(s.input_count, 200);
assert_eq!(s.kept_count, 50);
assert!((s.reduction_ratio - 0.75).abs() < 1e-12);
}