use crate::{Mat3, Point3, UnitVec3, Vec3};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Frame3 {
pub origin: Point3,
pub x: UnitVec3,
pub y: UnitVec3,
pub z: UnitVec3,
}
impl Frame3 {
pub const WORLD: Self = Self {
origin: Point3::ORIGIN,
x: UnitVec3::X,
y: UnitVec3::Y,
z: UnitVec3::Z,
};
pub fn from_origin_z(origin: Point3, forward: UnitVec3) -> Self {
let (x, y) = forward.perp_basis();
Self { origin, x, y, z: forward }
}
pub fn from_origin_z_up(origin: Point3, forward: UnitVec3, up_hint: Vec3) -> Self {
let up_proj = up_hint - forward.as_vec() * forward.dot_vec(up_hint);
let y = match UnitVec3::try_from_vec(up_proj) {
Some(u) => u,
None => forward.perp_basis().1, };
let x = UnitVec3::try_from_vec(y.as_vec().cross(forward.as_vec()))
.unwrap_or_else(|| forward.perp_basis().0);
Self { origin, x, y, z: forward }
}
#[inline]
pub fn to_local_point(self, p: Point3) -> Point3 {
let d = p - self.origin;
Point3::new(
self.x.dot_vec(d),
self.y.dot_vec(d),
self.z.dot_vec(d),
)
}
#[inline]
pub fn to_world_point(self, p: Point3) -> Point3 {
self.origin + self.x * p.x + self.y * p.y + self.z * p.z
}
#[inline]
pub fn to_local_vec(self, v: Vec3) -> Vec3 {
Vec3::new(self.x.dot_vec(v), self.y.dot_vec(v), self.z.dot_vec(v))
}
#[inline]
pub fn to_world_vec(self, v: Vec3) -> Vec3 {
self.x * v.x + self.y * v.y + self.z * v.z
}
#[inline]
pub fn rotation(self) -> Mat3 {
Mat3::from_axes(self.x, self.y, self.z)
}
#[inline]
pub fn translate(self, delta: Vec3) -> Self {
Self { origin: self.origin + delta, ..self }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_point() {
let frame = Frame3::from_origin_z(
Point3::new(1.0, 2.0, 3.0),
UnitVec3::try_from_vec(Vec3::new(1.0, 1.0, 1.0).normalize()).unwrap(),
);
let world_pt = Point3::new(5.0, 7.0, -2.0);
let local_pt = frame.to_local_point(world_pt);
let back = frame.to_world_point(local_pt);
assert!((world_pt - back).length() < 1e-10);
}
}