use crate::{InterferenceError, Metres};
fn canonical_zero(value: f64) -> f64 {
if value == 0.0 { 0.0 } else { value }
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Point3M {
x: Metres,
y: Metres,
z: Metres,
}
impl Point3M {
pub fn new(x: Metres, y: Metres, z: Metres) -> Self {
Self { x, y, z }
}
pub fn from_metres(x: f64, y: f64, z: f64) -> Result<Self, InterferenceError> {
Ok(Self::new(Metres::new(x)?, Metres::new(y)?, Metres::new(z)?))
}
pub fn x(self) -> Metres {
self.x
}
pub fn y(self) -> Metres {
self.y
}
pub fn z(self) -> Metres {
self.z
}
pub fn coordinates_metres(self) -> [f64; 3] {
[self.x.get(), self.y.get(), self.z.get()]
}
pub fn distance_to(self, other: Self) -> f64 {
let [x, y, z] = self.coordinates_metres();
let [other_x, other_y, other_z] = other.coordinates_metres();
(x - other_x).hypot(y - other_y).hypot(z - other_z)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct UnitVector3 {
x: f64,
y: f64,
z: f64,
}
impl UnitVector3 {
pub fn new(x: f64, y: f64, z: f64) -> Result<Self, InterferenceError> {
if !x.is_finite() || !y.is_finite() || !z.is_finite() {
return Err(InterferenceError::InvalidDirection { x, y, z });
}
let scale = x.abs().max(y.abs()).max(z.abs());
if scale == 0.0 {
return Err(InterferenceError::InvalidDirection { x, y, z });
}
let scaled_x = x / scale;
let scaled_y = y / scale;
let scaled_z = z / scale;
let norm = scaled_x.hypot(scaled_y).hypot(scaled_z);
Ok(Self {
x: canonical_zero(scaled_x / norm),
y: canonical_zero(scaled_y / norm),
z: canonical_zero(scaled_z / norm),
})
}
pub fn components(self) -> [f64; 3] {
[self.x, self.y, self.z]
}
pub fn signed_distance_metres(self, from: Point3M, to: Point3M) -> f64 {
let [from_x, from_y, from_z] = from.coordinates_metres();
let [to_x, to_y, to_z] = to.coordinates_metres();
self.x * (to_x - from_x) + self.y * (to_y - from_y) + self.z * (to_z - from_z)
}
}