#![forbid(unsafe_code)]
use axiolid_core::{Point3, Scalar, Vec3};
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum PointCloudError {
NonFinitePoint {
index: usize,
point: Point3,
},
ChannelLengthMismatch {
channel: &'static str,
found: usize,
expected: usize,
},
DegenerateNormal {
index: usize,
normal: Vec3,
},
NonFiniteIntensity {
index: usize,
intensity: Scalar,
},
}
impl core::fmt::Display for PointCloudError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NonFinitePoint { index, point } => {
write!(f, "point {index} is not finite: {point:?}")
}
Self::ChannelLengthMismatch {
channel,
found,
expected,
} => write!(
f,
"channel `{channel}` has {found} entries but the cloud has {expected} points"
),
Self::DegenerateNormal { index, normal } => {
write!(f, "normal {index} is not a usable direction: {normal:?}")
}
Self::NonFiniteIntensity { index, intensity } => {
write!(f, "intensity {index} is not finite: {intensity}")
}
}
}
}
impl core::error::Error for PointCloudError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Colour {
pub red: u8,
pub green: u8,
pub blue: u8,
}
impl Colour {
pub const fn new(red: u8, green: u8, blue: u8) -> Self {
Self { red, green, blue }
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct PointCloud {
points: Vec<Point3>,
normals: Option<Vec<Vec3>>,
colours: Option<Vec<Colour>>,
intensities: Option<Vec<Scalar>>,
}
impl PointCloud {
pub fn new(points: Vec<Point3>) -> Result<Self, PointCloudError> {
for (index, point) in points.iter().enumerate() {
if !point.is_finite() {
return Err(PointCloudError::NonFinitePoint {
index,
point: *point,
});
}
}
Ok(Self {
points,
normals: None,
colours: None,
intensities: None,
})
}
pub fn empty() -> Self {
Self::default()
}
pub fn with_normals(mut self, normals: Vec<Vec3>) -> Result<Self, PointCloudError> {
if normals.len() != self.points.len() {
return Err(PointCloudError::ChannelLengthMismatch {
channel: "normals",
found: normals.len(),
expected: self.points.len(),
});
}
for (index, normal) in normals.iter().enumerate() {
if !normal.is_finite() || normal.length() <= Scalar::MIN_POSITIVE {
return Err(PointCloudError::DegenerateNormal {
index,
normal: *normal,
});
}
}
self.normals = Some(normals);
Ok(self)
}
pub fn with_colours(mut self, colours: Vec<Colour>) -> Result<Self, PointCloudError> {
if colours.len() != self.points.len() {
return Err(PointCloudError::ChannelLengthMismatch {
channel: "colours",
found: colours.len(),
expected: self.points.len(),
});
}
self.colours = Some(colours);
Ok(self)
}
pub fn with_intensities(mut self, intensities: Vec<Scalar>) -> Result<Self, PointCloudError> {
if intensities.len() != self.points.len() {
return Err(PointCloudError::ChannelLengthMismatch {
channel: "intensities",
found: intensities.len(),
expected: self.points.len(),
});
}
for (index, intensity) in intensities.iter().enumerate() {
if !intensity.is_finite() {
return Err(PointCloudError::NonFiniteIntensity {
index,
intensity: *intensity,
});
}
}
self.intensities = Some(intensities);
Ok(self)
}
pub fn points(&self) -> &[Point3] {
&self.points
}
pub fn normals(&self) -> Option<&[Vec3]> {
self.normals.as_deref()
}
pub fn colours(&self) -> Option<&[Colour]> {
self.colours.as_deref()
}
pub fn intensities(&self) -> Option<&[Scalar]> {
self.intensities.as_deref()
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn has_normals(&self) -> bool {
self.normals.is_some()
}
pub fn bounds(&self) -> Option<(Point3, Point3)> {
let first = *self.points.first()?;
let mut min = first;
let mut max = first;
for p in &self.points[1..] {
min = Point3::new(min.x.min(p.x), min.y.min(p.y), min.z.min(p.z));
max = Point3::new(max.x.max(p.x), max.y.max(p.y), max.z.max(p.z));
}
Some((min, max))
}
}