1use crate::{Mat4, Quat, Vec3};
2
3#[derive(Clone, Copy, Debug, PartialEq)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Transform {
7 pub translation: Vec3,
9 pub rotation: Quat,
11 pub scale: Vec3,
13}
14
15impl Transform {
16 pub const IDENTITY: Self = Self::new(Vec3::ZERO, Quat::IDENTITY, Vec3::ONE);
18
19 #[inline]
21 pub const fn new(translation: Vec3, rotation: Quat, scale: Vec3) -> Self {
22 Self {
23 translation,
24 rotation,
25 scale,
26 }
27 }
28
29 #[inline]
31 pub const fn from_translation(value: Vec3) -> Self {
32 Self::new(value, Quat::IDENTITY, Vec3::ONE)
33 }
34
35 #[inline]
37 pub const fn from_rotation(value: Quat) -> Self {
38 Self::new(Vec3::ZERO, value, Vec3::ONE)
39 }
40
41 #[inline]
43 pub const fn from_scale(value: Vec3) -> Self {
44 Self::new(Vec3::ZERO, Quat::IDENTITY, value)
45 }
46
47 pub fn looking_at(eye: Vec3, target: Vec3, up: Vec3) -> Self {
49 let view = Mat4::look_at(eye, target, up);
50 let world = view.inverse().unwrap_or(Mat4::from_translation(eye));
51 world.decompose().unwrap_or(Self::from_translation(eye))
52 }
53
54 #[inline]
56 pub fn to_mat4(self) -> Mat4 {
57 Mat4::from_trs(self.translation, self.rotation, self.scale)
58 }
59
60 #[inline]
62 pub fn from_mat4(matrix: Mat4) -> Option<Self> {
63 matrix.decompose()
64 }
65
66 #[inline]
68 pub fn mul_transform(self, rhs: Self) -> Self {
69 Self::from_mat4(self.to_mat4() * rhs.to_mat4()).unwrap_or(Self::IDENTITY)
70 }
71
72 #[inline]
74 pub fn inverse(self) -> Self {
75 self.to_mat4()
76 .inverse()
77 .and_then(Self::from_mat4)
78 .unwrap_or(Self::IDENTITY)
79 }
80
81 #[inline]
83 pub fn forward(self) -> Vec3 {
84 self.rotation.mul_vec3(Vec3::NEG_Z).normalize()
85 }
86
87 #[inline]
89 pub fn right(self) -> Vec3 {
90 self.rotation.mul_vec3(Vec3::X).normalize()
91 }
92
93 #[inline]
95 pub fn up(self) -> Vec3 {
96 self.rotation.mul_vec3(Vec3::Y).normalize()
97 }
98
99 #[inline]
101 pub fn translate_by(mut self, delta: Vec3) -> Self {
102 self.translation += delta;
103 self
104 }
105
106 #[inline]
108 pub fn rotate_by(mut self, rotation: Quat) -> Self {
109 self.rotation = (self.rotation * rotation).normalize();
110 self
111 }
112
113 #[inline]
115 pub fn scale_by(mut self, scale: Vec3) -> Self {
116 self.scale = self.scale.mul_elements(scale);
117 self
118 }
119}
120
121impl Default for Transform {
122 #[inline]
123 fn default() -> Self {
124 Self::IDENTITY
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131 use crate::assert_close;
132
133 #[test]
134 fn transform_matrix_decomposes_back_to_trs() {
135 let transform = Transform::new(
136 Vec3::new(1.0, 2.0, 3.0),
137 Quat::from_axis_angle(Vec3::Y, 0.8),
138 Vec3::new(2.0, 2.0, 2.0),
139 );
140 let out = Transform::from_mat4(transform.to_mat4()).unwrap();
141 assert_close(out.translation.x, transform.translation.x);
142 assert_close(out.translation.y, transform.translation.y);
143 assert_close(out.translation.z, transform.translation.z);
144 assert_close(out.scale.x, transform.scale.x);
145 assert_close(out.rotation.angle_between(transform.rotation), 0.0);
146 }
147
148 #[test]
149 fn inverse_undoes_transform() {
150 let transform = Transform::new(
151 Vec3::new(1.0, 2.0, 3.0),
152 Quat::from_axis_angle(Vec3::Y, 0.6),
153 Vec3::new(2.0, 2.0, 2.0),
154 );
155 let point = Vec3::new(4.0, 5.0, 6.0);
156 let moved = transform.to_mat4().mul_vec3(point);
157 let restored = transform.inverse().to_mat4().mul_vec3(moved);
158 assert_close(restored.x, point.x);
159 assert_close(restored.y, point.y);
160 assert_close(restored.z, point.z);
161 }
162
163 #[test]
164 fn direction_vectors_follow_rotation() {
165 let transform =
166 Transform::from_rotation(Quat::from_axis_angle(Vec3::Y, core::f32::consts::FRAC_PI_2));
167 assert_close(transform.forward().x, -1.0);
168 assert_close(transform.right().z, -1.0);
169 assert_close(transform.up().y, 1.0);
170 }
171}