use num_traits::ToPrimitive;
#[derive(Clone, Copy, Debug)]
pub struct CoordinateSample<'a> {
start: &'a [[f32; 3]],
end: &'a [[f32; 3]],
weight: f32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum CoordinateSampleError {
#[error("coordinate sample requires equal-length columns")]
Shape,
#[error("coordinate sample weight must be finite and between zero and one")]
Weight,
}
impl<'a> CoordinateSample<'a> {
pub fn new(
start: &'a [[f32; 3]],
end: &'a [[f32; 3]],
weight: f32,
) -> Result<Self, CoordinateSampleError> {
if start.len() != end.len() {
return Err(CoordinateSampleError::Shape);
}
if !weight.is_finite() || !(0.0..=1.0).contains(&weight) {
return Err(CoordinateSampleError::Weight);
}
Ok(Self { start, end, weight })
}
#[must_use]
pub const fn len(self) -> usize {
self.start.len()
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.start.is_empty()
}
#[must_use]
pub fn position(self, row: usize) -> Option<[f32; 3]> {
let start = *self.start.get(row)?;
let end = *self.end.get(row)?;
if !start.iter().chain(&end).all(|value| value.is_finite()) {
return None;
}
if self.weight.to_bits() == 0.0f32.to_bits() || self.weight.to_bits() == (-0.0f32).to_bits()
{
return Some(start);
}
if self.weight.to_bits() == 1.0f32.to_bits() {
return Some(end);
}
let weight = f64::from(self.weight);
let mut output = [0.0; 3];
for axis in 0..3 {
output[axis] = (f64::from(start[axis]) * (1.0 - weight)
+ f64::from(end[axis]) * weight)
.to_f32()?;
}
Some(output)
}
}
#[cfg(test)]
#[path = "sample_tests.rs"]
mod tests;