use crate::plane_frame::FrameError;
use crate::primitives::{Point3, Vec3};
use crate::scalar::{Scalar, Tolerance};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpaceFrame {
origin: Point3,
x: Vec3,
y: Vec3,
z: Vec3,
}
impl SpaceFrame {
#[must_use]
pub const fn world() -> Self {
Self {
origin: Point3::new(0.0, 0.0, 0.0),
x: Vec3::new(1.0, 0.0, 0.0),
y: Vec3::new(0.0, 1.0, 0.0),
z: Vec3::new(0.0, 0.0, 1.0),
}
}
pub fn new(
origin: Point3,
x: Vec3,
y: Vec3,
z: Vec3,
tolerance: Tolerance,
) -> Result<Self, FrameError> {
if !origin.is_finite() || !x.is_finite() || !y.is_finite() || !z.is_finite() {
return Err(FrameError::NonFiniteInput);
}
let unit = tolerance.angular().max(Scalar::EPSILON * 8.0);
let lengths = [x.length_squared(), y.length_squared(), z.length_squared()];
if lengths.iter().any(|l| (l - 1.0).abs() > unit) {
return Err(FrameError::NotUnitLength);
}
if x.dot(y).abs() > unit || y.dot(z).abs() > unit || z.dot(x).abs() > unit {
return Err(FrameError::NotPerpendicular);
}
if (x.cross(y).dot(z) - 1.0).abs() > unit {
return Err(FrameError::NotRightHanded);
}
Ok(Self { origin, x, y, z })
}
#[must_use]
pub fn to_local(&self, point: Point3) -> Vec3 {
let offset = point - self.origin;
Vec3::new(offset.dot(self.x), offset.dot(self.y), offset.dot(self.z))
}
#[must_use]
pub fn to_world(&self, local: Vec3) -> Point3 {
self.origin + self.x * local.x + self.y * local.y + self.z * local.z
}
#[must_use]
pub const fn origin(&self) -> Point3 {
self.origin
}
#[must_use]
pub const fn x(&self) -> Vec3 {
self.x
}
#[must_use]
pub const fn y(&self) -> Vec3 {
self.y
}
#[must_use]
pub const fn z(&self) -> Vec3 {
self.z
}
}