#[cfg(test)]
mod tests {
use crate::point_cloud_io::functions::{cross3, dist, normalize3};
use crate::point_cloud_io::*;
fn make_cube_cloud(n_per_face: usize) -> PointCloud {
let mut cloud = PointCloud::new();
let step = 1.0 / n_per_face as f64;
for i in 0..n_per_face {
for j in 0..n_per_face {
let u = i as f64 * step;
let v = j as f64 * step;
cloud.positions.push([u, v, 0.0]);
cloud.positions.push([u, v, 1.0]);
cloud.positions.push([0.0, u, v]);
cloud.positions.push([1.0, u, v]);
cloud.positions.push([u, 0.0, v]);
cloud.positions.push([u, 1.0, v]);
}
}
cloud
}
fn make_simple_cloud() -> PointCloud {
PointCloud::from_positions(vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 1.0, 0.0],
[1.0, 0.0, 1.0],
[0.0, 1.0, 1.0],
[1.0, 1.0, 1.0],
])
}
#[test]
fn test_point_cloud_new() {
let cloud = PointCloud::new();
assert!(cloud.is_empty());
assert_eq!(cloud.len(), 0);
}
#[test]
fn test_point_cloud_from_positions() {
let cloud = make_simple_cloud();
assert_eq!(cloud.len(), 8);
assert!(!cloud.is_empty());
assert!(!cloud.has_normals());
assert!(!cloud.has_colors());
}
#[test]
fn test_point_cloud_add_point() {
let mut cloud = PointCloud::new();
cloud.add_point([1.0, 2.0, 3.0]);
assert_eq!(cloud.len(), 1);
assert!((cloud.positions[0][0] - 1.0).abs() < 1e-10);
}
#[test]
fn test_point_cloud_get_point() {
let cloud = make_simple_cloud();
let pt = cloud.get_point(0).unwrap();
assert!((pt.position[0]).abs() < 1e-10);
assert!(cloud.get_point(100).is_none());
}
#[test]
fn test_bounding_box_basic() {
let cloud = make_simple_cloud();
let bb = BoundingBox::from_point_cloud(&cloud).unwrap();
assert!((bb.min[0]).abs() < 1e-10);
assert!((bb.max[0] - 1.0).abs() < 1e-10);
assert!((bb.max[2] - 1.0).abs() < 1e-10);
}
#[test]
fn test_bounding_box_center() {
let cloud = make_simple_cloud();
let bb = BoundingBox::from_point_cloud(&cloud).unwrap();
let c = bb.center();
assert!((c[0] - 0.5).abs() < 1e-10);
assert!((c[1] - 0.5).abs() < 1e-10);
assert!((c[2] - 0.5).abs() < 1e-10);
}
#[test]
fn test_bounding_box_volume() {
let cloud = make_simple_cloud();
let bb = BoundingBox::from_point_cloud(&cloud).unwrap();
assert!((bb.volume() - 1.0).abs() < 1e-10);
}
#[test]
fn test_bounding_box_diagonal() {
let cloud = make_simple_cloud();
let bb = BoundingBox::from_point_cloud(&cloud).unwrap();
let expected = (3.0_f64).sqrt();
assert!((bb.diagonal() - expected).abs() < 1e-10);
}
#[test]
fn test_bounding_box_contains() {
let cloud = make_simple_cloud();
let bb = BoundingBox::from_point_cloud(&cloud).unwrap();
assert!(bb.contains([0.5, 0.5, 0.5]));
assert!(!bb.contains([2.0, 0.0, 0.0]));
}
#[test]
fn test_bounding_box_merge() {
let bb1 = BoundingBox {
min: [0.0, 0.0, 0.0],
max: [1.0, 1.0, 1.0],
};
let bb2 = BoundingBox {
min: [-1.0, -1.0, -1.0],
max: [0.5, 0.5, 0.5],
};
let merged = bb1.merge(&bb2);
assert!((merged.min[0] + 1.0).abs() < 1e-10);
assert!((merged.max[0] - 1.0).abs() < 1e-10);
}
#[test]
fn test_kdtree_nearest() {
let cloud = make_simple_cloud();
let tree = KdTree::build(&cloud);
let (idx, d_sq) = tree.nearest([0.1, 0.1, 0.1]).unwrap();
assert_eq!(idx, 0);
assert!(d_sq < 0.1);
}
#[test]
fn test_kdtree_k_nearest() {
let cloud = make_simple_cloud();
let tree = KdTree::build(&cloud);
let results = tree.k_nearest([0.0, 0.0, 0.0], 3);
assert_eq!(results.len(), 3);
assert_eq!(results[0].0, 0);
assert!(results[0].1 < 1e-10);
}
#[test]
fn test_kdtree_radius_search() {
let cloud = make_simple_cloud();
let tree = KdTree::build(&cloud);
let results = tree.radius_search([0.0, 0.0, 0.0], 1.01);
assert!(!results.is_empty());
assert!(results.len() <= 4);
}
#[test]
fn test_kdtree_empty() {
let cloud = PointCloud::new();
let tree = KdTree::build(&cloud);
assert!(tree.is_empty());
assert!(tree.nearest([0.0, 0.0, 0.0]).is_none());
}
#[test]
fn test_voxel_grid_downsample() {
let cloud = make_cube_cloud(10);
assert!(cloud.len() > 100);
let downsampled = voxel_grid_downsample(&cloud, 0.5);
assert!(downsampled.len() < cloud.len());
assert!(!downsampled.is_empty());
}
#[test]
fn test_voxel_grid_preserves_bounds() {
let cloud = make_simple_cloud();
let bb_orig = BoundingBox::from_point_cloud(&cloud).unwrap();
let downsampled = voxel_grid_downsample(&cloud, 0.5);
let bb_down = BoundingBox::from_point_cloud(&downsampled).unwrap();
assert!(bb_down.min[0] >= bb_orig.min[0] - 0.5);
assert!(bb_down.max[0] <= bb_orig.max[0] + 0.5);
}
#[test]
fn test_statistical_outlier_removal() {
let mut cloud = make_simple_cloud();
cloud.positions.push([100.0, 100.0, 100.0]);
let filtered = statistical_outlier_removal(&cloud, 3, 1.0);
assert!(filtered.len() <= cloud.len());
for p in &filtered.positions {
assert!(p[0] < 50.0, "Outlier not removed: {p:?}");
}
}
#[test]
fn test_estimate_normals() {
let mut cloud = PointCloud::new();
for i in 0..10 {
for j in 0..10 {
cloud.positions.push([i as f64, j as f64, 0.0]);
}
}
let normals = estimate_normals(&cloud, 6);
assert_eq!(normals.len(), 100);
let mut z_aligned = 0;
for n in &normals {
if n[2].abs() > 0.5 {
z_aligned += 1;
}
}
assert!(z_aligned > 50, "Only {z_aligned}/100 normals z-aligned");
}
#[test]
fn test_compute_centroid() {
let cloud = make_simple_cloud();
let c = compute_centroid(&cloud);
assert!((c[0] - 0.5).abs() < 1e-10);
assert!((c[1] - 0.5).abs() < 1e-10);
assert!((c[2] - 0.5).abs() < 1e-10);
}
#[test]
fn test_merge_point_clouds() {
let a = PointCloud::from_positions(vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]);
let b = PointCloud::from_positions(vec![[2.0, 0.0, 0.0], [3.0, 0.0, 0.0]]);
let merged = merge_point_clouds(&a, &b);
assert_eq!(merged.len(), 4);
assert!((merged.positions[2][0] - 2.0).abs() < 1e-10);
}
#[test]
fn test_transform_point_cloud() {
let cloud = PointCloud::from_positions(vec![[1.0, 0.0, 0.0]]);
let rot = [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]];
let trans = [0.0, 0.0, 0.0];
let result = transform_point_cloud(&cloud, rot, trans);
assert!((result.positions[0][0]).abs() < 1e-10);
assert!((result.positions[0][1] - 1.0).abs() < 1e-10);
}
#[test]
fn test_scale_point_cloud() {
let cloud = PointCloud::from_positions(vec![[1.0, 2.0, 3.0]]);
let scaled = scale_point_cloud(&cloud, 2.0);
assert!((scaled.positions[0][0] - 2.0).abs() < 1e-10);
assert!((scaled.positions[0][1] - 4.0).abs() < 1e-10);
}
#[test]
fn test_translate_point_cloud() {
let cloud = PointCloud::from_positions(vec![[1.0, 2.0, 3.0]]);
let translated = translate_point_cloud(&cloud, [10.0, 20.0, 30.0]);
assert!((translated.positions[0][0] - 11.0).abs() < 1e-10);
}
#[test]
fn test_center_point_cloud() {
let cloud = PointCloud::from_positions(vec![[2.0, 4.0, 6.0], [4.0, 6.0, 8.0]]);
let centered = center_point_cloud(&cloud);
let c = compute_centroid(¢ered);
assert!(c[0].abs() < 1e-10);
assert!(c[1].abs() < 1e-10);
assert!(c[2].abs() < 1e-10);
}
#[test]
fn test_write_parse_ply_ascii() {
let mut cloud = PointCloud::from_positions(vec![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]);
cloud.normals = Some(vec![[0.0, 0.0, 1.0], [0.0, 1.0, 0.0]]);
let mut buf = Vec::new();
write_ply_ascii(&cloud, &mut buf).unwrap();
let text = String::from_utf8(buf).unwrap();
let parsed = parse_ply_ascii(&text).unwrap();
assert_eq!(parsed.len(), 2);
assert!((parsed.positions[0][0] - 1.0).abs() < 0.001);
assert!(parsed.has_normals());
}
#[test]
fn test_write_ply_binary_le() {
let cloud = PointCloud::from_positions(vec![[1.0, 2.0, 3.0]]);
let buf = write_ply_binary_le(&cloud);
assert!(buf.starts_with(b"ply"));
assert!(buf.len() > 12);
}
#[test]
fn test_write_parse_pcd_ascii() {
let cloud =
PointCloud::from_positions(vec![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]);
let mut buf = Vec::new();
write_pcd_ascii(&cloud, &mut buf).unwrap();
let text = String::from_utf8(buf).unwrap();
let parsed = parse_pcd_ascii(&text).unwrap();
assert_eq!(parsed.len(), 3);
assert!((parsed.positions[1][0] - 4.0).abs() < 0.001);
}
#[test]
fn test_write_parse_xyz() {
let mut cloud = PointCloud::from_positions(vec![[1.0, 2.0, 3.0]]);
cloud.normals = Some(vec![[0.0, 0.0, 1.0]]);
cloud.colors = Some(vec![[255, 128, 0]]);
let mut buf = Vec::new();
write_xyz(&cloud, &mut buf).unwrap();
let text = String::from_utf8(buf).unwrap();
let parsed = parse_xyz(&text);
assert_eq!(parsed.len(), 1);
assert!((parsed.positions[0][0] - 1.0).abs() < 0.001);
}
#[test]
fn test_parse_xyz_basic() {
let data = "1.0 2.0 3.0\n4.0 5.0 6.0\n";
let cloud = parse_xyz(data);
assert_eq!(cloud.len(), 2);
assert!(!cloud.has_normals());
}
#[test]
fn test_las_header_mock() {
let header = LasHeader::mock(50000);
assert_eq!(header.num_points, 50000);
assert_eq!(header.file_signature, "LASF");
assert!(header.bounding_volume() > 0.0);
}
#[test]
fn test_e57_structure() {
let e57 = E57File::mock();
assert_eq!(e57.num_scans(), 1);
assert_eq!(e57.total_points(), 1000);
}
#[test]
fn test_ball_pivoting_mesh() {
let mut cloud = PointCloud::new();
for i in 0..5 {
for j in 0..5 {
cloud.positions.push([i as f64 * 0.5, j as f64 * 0.5, 0.0]);
}
}
let triangles = ball_pivoting_mesh(&cloud, 0.6);
assert!(!triangles.is_empty(), "Should produce some triangles");
for tri in &triangles {
assert!(tri.indices[0] < cloud.len());
assert!(tri.indices[1] < cloud.len());
assert!(tri.indices[2] < cloud.len());
}
}
#[test]
fn test_dist_sq_and_dist() {
let a = [1.0, 0.0, 0.0];
let b = [4.0, 0.0, 0.0];
assert!((dist_sq(a, b) - 9.0).abs() < 1e-10);
assert!((dist(a, b) - 3.0).abs() < 1e-10);
}
#[test]
fn test_normalize3() {
let v = [3.0, 4.0, 0.0];
let n = normalize3(v);
assert!((n[0] - 0.6).abs() < 1e-10);
assert!((n[1] - 0.8).abs() < 1e-10);
let z = normalize3([0.0, 0.0, 0.0]);
assert!(z[0].abs() < 1e-10);
}
#[test]
fn test_cross3() {
let a = [1.0, 0.0, 0.0];
let b = [0.0, 1.0, 0.0];
let c = cross3(a, b);
assert!((c[2] - 1.0).abs() < 1e-10);
}
#[test]
fn test_kdtree_large() {
let mut cloud = PointCloud::new();
for i in 0..100 {
cloud
.positions
.push([(i % 10) as f64, (i / 10) as f64, 0.0]);
}
let tree = KdTree::build(&cloud);
assert_eq!(tree.len(), 100);
let (idx, _d) = tree.nearest([4.5, 4.5, 0.0]).unwrap();
let p = cloud.positions[idx];
assert!((p[0] - 4.5).abs() <= 1.0);
assert!((p[1] - 4.5).abs() <= 1.0);
}
#[test]
fn test_point_cloud_reserve() {
let mut cloud = PointCloud::new();
cloud.reserve(100);
assert!(cloud.is_empty());
cloud.add_point([1.0, 2.0, 3.0]);
assert_eq!(cloud.len(), 1);
}
#[test]
fn test_bounding_box_empty() {
let cloud = PointCloud::new();
assert!(BoundingBox::from_point_cloud(&cloud).is_none());
}
}