use axiolid_core::{Point3, Scalar, Vec3};
use axiolid_pointcloud::{Colour, PointCloud, PointCloudError};
fn sample_points() -> Vec<Point3> {
vec![
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
]
}
#[test]
fn finite_points_are_accepted_in_the_given_order() {
let cloud = PointCloud::new(sample_points()).expect("finite points construct");
assert_eq!(cloud.len(), 3);
assert!(!cloud.is_empty());
assert_eq!(cloud.points()[1], Point3::new(1.0, 0.0, 0.0));
assert!(!cloud.has_normals());
}
#[test]
fn an_empty_cloud_is_legitimate() {
let cloud = PointCloud::empty();
assert!(cloud.is_empty());
assert_eq!(cloud.len(), 0);
assert_eq!(cloud.bounds(), None);
let also_empty = PointCloud::new(Vec::new()).expect("an empty vector constructs");
assert!(also_empty.is_empty());
}
#[test]
fn a_non_finite_point_is_refused_by_index() {
let mut points = sample_points();
points.push(Point3::new(0.0, Scalar::NAN, 0.0));
let error = PointCloud::new(points).expect_err("NaN must be refused");
assert!(
matches!(error, PointCloudError::NonFinitePoint { index: 3, .. }),
"the offending index must be reported, got {error:?}"
);
let mut infinite = sample_points();
infinite[0] = Point3::new(Scalar::INFINITY, 0.0, 0.0);
assert!(matches!(
PointCloud::new(infinite),
Err(PointCloudError::NonFinitePoint { index: 0, .. })
));
}
#[test]
fn a_channel_of_the_wrong_length_is_refused() {
let cloud = PointCloud::new(sample_points()).expect("constructs");
let error = cloud
.clone()
.with_normals(vec![Vec3::new(0.0, 0.0, 1.0)])
.expect_err("one normal for three points must be refused");
assert!(
matches!(
error,
PointCloudError::ChannelLengthMismatch {
channel: "normals",
found: 1,
expected: 3
}
),
"the mismatch must name the channel and both counts, got {error:?}"
);
assert!(matches!(
cloud.clone().with_colours(vec![Colour::new(1, 2, 3)]),
Err(PointCloudError::ChannelLengthMismatch {
channel: "colours",
..
})
));
assert!(matches!(
cloud.with_intensities(vec![0.5]),
Err(PointCloudError::ChannelLengthMismatch {
channel: "intensities",
..
})
));
}
#[test]
fn a_degenerate_normal_is_refused_rather_than_invented() {
let cloud = PointCloud::new(sample_points()).expect("constructs");
let error = cloud
.clone()
.with_normals(vec![
Vec3::new(0.0, 0.0, 1.0),
Vec3::ZERO,
Vec3::new(1.0, 0.0, 0.0),
])
.expect_err("a zero normal must be refused");
assert!(
matches!(error, PointCloudError::DegenerateNormal { index: 1, .. }),
"the offending normal must be named, got {error:?}"
);
assert!(matches!(
cloud.with_normals(vec![
Vec3::new(0.0, 0.0, 1.0),
Vec3::new(Scalar::NAN, 0.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
]),
Err(PointCloudError::DegenerateNormal { index: 1, .. })
));
}
#[test]
fn attributes_attach_and_read_back() {
let cloud = PointCloud::new(sample_points())
.expect("constructs")
.with_normals(vec![
Vec3::new(0.0, 0.0, 1.0),
Vec3::new(0.0, 1.0, 0.0),
Vec3::new(1.0, 0.0, 0.0),
])
.expect("normals attach")
.with_colours(vec![
Colour::new(255, 0, 0),
Colour::new(0, 255, 0),
Colour::new(0, 0, 255),
])
.expect("colours attach")
.with_intensities(vec![0.1, 0.5, 0.9])
.expect("intensities attach");
assert!(cloud.has_normals());
assert_eq!(
cloud.normals().expect("normals")[2],
Vec3::new(1.0, 0.0, 0.0)
);
assert_eq!(cloud.colours().expect("colours")[0], Colour::new(255, 0, 0));
assert_eq!(cloud.intensities().expect("intensities")[1], 0.5);
}
#[test]
fn absent_channels_are_absent_not_empty() {
let cloud = PointCloud::new(sample_points()).expect("constructs");
assert_eq!(cloud.normals(), None);
assert_eq!(cloud.colours(), None);
assert_eq!(cloud.intensities(), None);
}
#[test]
fn normals_are_not_silently_renormalised() {
let cloud = PointCloud::new(sample_points())
.expect("constructs")
.with_normals(vec![
Vec3::new(0.0, 0.0, 5.0),
Vec3::new(0.0, 2.0, 0.0),
Vec3::new(0.5, 0.0, 0.0),
])
.expect("normals attach");
assert_eq!(
cloud.normals().expect("normals")[0],
Vec3::new(0.0, 0.0, 5.0),
"a normal must be stored as supplied"
);
}
#[test]
fn bounds_enclose_every_point() {
let cloud = PointCloud::new(vec![
Point3::new(-1.0, 2.0, 0.5),
Point3::new(3.0, -4.0, 0.0),
Point3::new(0.0, 0.0, 7.0),
])
.expect("constructs");
let (min, max) = cloud.bounds().expect("a non-empty cloud has bounds");
assert_eq!(min, Point3::new(-1.0, -4.0, 0.0));
assert_eq!(max, Point3::new(3.0, 2.0, 7.0));
}
#[test]
fn duplicate_points_are_preserved() {
let repeated = Point3::new(1.0, 1.0, 1.0);
let cloud = PointCloud::new(vec![repeated, repeated, repeated]).expect("constructs");
assert_eq!(cloud.len(), 3, "duplicates must not be collapsed");
}