1use crate::{Mat3, Point3, UnitVec3, Vec3};
8
9#[derive(Clone, Copy, Debug, PartialEq)]
13pub struct Frame3 {
14 pub origin: Point3,
16 pub x: UnitVec3,
18 pub y: UnitVec3,
20 pub z: UnitVec3,
22}
23
24impl Frame3 {
25 pub const WORLD: Self = Self {
27 origin: Point3::ORIGIN,
28 x: UnitVec3::X,
29 y: UnitVec3::Y,
30 z: UnitVec3::Z,
31 };
32
33 pub fn from_origin_z(origin: Point3, forward: UnitVec3) -> Self {
38 let (x, y) = forward.perp_basis();
39 Self { origin, x, y, z: forward }
40 }
41
42 pub fn from_origin_z_up(origin: Point3, forward: UnitVec3, up_hint: Vec3) -> Self {
48 let up_proj = up_hint - forward.as_vec() * forward.dot_vec(up_hint);
49 let y = match UnitVec3::try_from_vec(up_proj) {
50 Some(u) => u,
51 None => forward.perp_basis().1, };
53 let x = UnitVec3::try_from_vec(y.as_vec().cross(forward.as_vec()))
54 .unwrap_or_else(|| forward.perp_basis().0);
55 Self { origin, x, y, z: forward }
56 }
57
58 #[inline]
60 pub fn to_local_point(self, p: Point3) -> Point3 {
61 let d = p - self.origin;
62 Point3::new(
63 self.x.dot_vec(d),
64 self.y.dot_vec(d),
65 self.z.dot_vec(d),
66 )
67 }
68
69 #[inline]
71 pub fn to_world_point(self, p: Point3) -> Point3 {
72 self.origin + self.x * p.x + self.y * p.y + self.z * p.z
73 }
74
75 #[inline]
77 pub fn to_local_vec(self, v: Vec3) -> Vec3 {
78 Vec3::new(self.x.dot_vec(v), self.y.dot_vec(v), self.z.dot_vec(v))
79 }
80
81 #[inline]
83 pub fn to_world_vec(self, v: Vec3) -> Vec3 {
84 self.x * v.x + self.y * v.y + self.z * v.z
85 }
86
87 #[inline]
89 pub fn rotation(self) -> Mat3 {
90 Mat3::from_axes(self.x, self.y, self.z)
91 }
92
93 #[inline]
95 pub fn translate(self, delta: Vec3) -> Self {
96 Self { origin: self.origin + delta, ..self }
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn round_trip_point() {
106 let frame = Frame3::from_origin_z(
107 Point3::new(1.0, 2.0, 3.0),
108 UnitVec3::try_from_vec(Vec3::new(1.0, 1.0, 1.0).normalize()).unwrap(),
109 );
110 let world_pt = Point3::new(5.0, 7.0, -2.0);
111 let local_pt = frame.to_local_point(world_pt);
112 let back = frame.to_world_point(local_pt);
113 assert!((world_pt - back).length() < 1e-10);
114 }
115}