1use crate::vector::*;
4use crate::rotator::*;
5use crate::BinarySerializable;
6use glam::Mat4;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
17pub struct Transform {
18 pub location: Vector,
20 pub rotation: Quaternion,
22 pub scale: Vector,
24}
25
26impl fmt::Display for Transform {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(
29 f,
30 "Location: ({:.2}, {:.2}, {:.2}), Rotation: {}, Scale: ({:.2}, {:.2}, {:.2})",
31 self.location.x, self.location.y, self.location.z,
32 self.get_rotator(),
33 self.scale.x, self.scale.y, self.scale.z
34 )
35 }
36}
37
38impl BinarySerializable for Transform {}
39
40impl Transform {
41 pub const IDENTITY: Self = Self {
43 location: Vector::ZERO,
44 rotation: Quaternion::IDENTITY,
45 scale: Vector::ONE,
46 };
47
48 pub fn new(location: Vector, rotation: Quaternion, scale: Vector) -> Self {
50 Self { location, rotation, scale }
51 }
52
53 pub fn from_location(location: Vector) -> Self {
55 Self {
56 location,
57 ..Self::IDENTITY
58 }
59 }
60
61 pub fn from_rotation(rotation: Quaternion) -> Self {
63 Self {
64 rotation,
65 ..Self::IDENTITY
66 }
67 }
68
69 pub fn from_scale(scale: Vector) -> Self {
71 Self {
72 scale,
73 ..Self::IDENTITY
74 }
75 }
76
77 pub fn from_uniform_scale(scale: f32) -> Self {
79 Self {
80 scale: Vector::splat(scale),
81 ..Self::IDENTITY
82 }
83 }
84
85 pub fn from_location_rotator(location: Vector, rotator: Rotator) -> Self {
87 Self {
88 location,
89 rotation: rotator.to_quaternion(),
90 scale: Vector::ONE,
91 }
92 }
93
94 pub fn from_location_rotator_scale(location: Vector, rotator: Rotator, scale: Vector) -> Self {
96 Self {
97 location,
98 rotation: rotator.to_quaternion(),
99 scale,
100 }
101 }
102
103 pub fn to_matrix(self) -> Matrix4 {
105 Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.location)
106 }
107
108 pub fn from_matrix(matrix: Matrix4) -> Self {
110 let (scale, rotation, location) = matrix.to_scale_rotation_translation();
111 Self { location, rotation, scale }
112 }
113
114 pub fn get_rotator(self) -> Rotator {
116 Rotator::from_quaternion(self.rotation)
117 }
118
119 pub fn set_rotator(&mut self, rotator: Rotator) {
121 self.rotation = rotator.to_quaternion();
122 }
123
124 pub fn transform_point(self, point: Vector) -> Vector {
126 self.to_matrix().transform_point3(point)
127 }
128
129 pub fn transform_vector(self, vector: Vector) -> Vector {
131 self.rotation * (vector * self.scale)
132 }
133
134 pub fn transform_direction(self, direction: Vector) -> Vector {
136 self.rotation * direction
137 }
138
139 pub fn inverse(self) -> Self {
141 let inv_matrix = self.to_matrix().inverse();
142 Self::from_matrix(inv_matrix)
143 }
144
145 pub fn combine(self, other: Transform) -> Self {
147 let combined_matrix = other.to_matrix() * self.to_matrix();
148 Self::from_matrix(combined_matrix)
149 }
150
151 pub fn get_forward_vector(self) -> Vector {
153 self.transform_direction(VectorConstants::FORWARD)
154 }
155
156 pub fn get_right_vector(self) -> Vector {
158 self.transform_direction(VectorConstants::RIGHT)
159 }
160
161 pub fn get_up_vector(self) -> Vector {
163 self.transform_direction(VectorConstants::UP)
164 }
165
166 pub fn is_nearly_equal(self, other: Transform, tolerance: f32) -> bool {
168 (self.location - other.location).length() <= tolerance
169 && self.rotation.abs_diff_eq(other.rotation, tolerance)
170 && (self.scale - other.scale).length() <= tolerance
171 }
172
173 pub fn is_nearly_identity(self, tolerance: f32) -> bool {
175 self.is_nearly_equal(Self::IDENTITY, tolerance)
176 }
177
178 pub fn lerp(self, other: Transform, alpha: f32) -> Self {
180 Self {
181 location: self.location.lerp(other.location, alpha),
182 rotation: self.rotation.slerp(other.rotation, alpha),
183 scale: self.scale.lerp(other.scale, alpha),
184 }
185 }
186
187 pub fn add_location(mut self, delta: Vector) -> Self {
189 self.location += delta;
190 self
191 }
192
193 pub fn add_rotation(mut self, delta_rotation: Quaternion) -> Self {
195 self.rotation = delta_rotation * self.rotation;
196 self
197 }
198
199 pub fn add_uniform_scale(mut self, delta_scale: f32) -> Self {
201 self.scale *= delta_scale;
202 self
203 }
204}
205
206impl Default for Transform {
207 fn default() -> Self {
208 Self::IDENTITY
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_transform_identity() {
218 let transform = Transform::IDENTITY;
219 let point = Vector::new(1.0, 2.0, 3.0);
220 let transformed = transform.transform_point(point);
221
222 assert!((transformed - point).length() < 0.001);
224 }
225
226 #[test]
227 fn test_transform_location() {
228 let transform = Transform::from_location(Vector::new(10.0, 20.0, 30.0));
229 let point = Vector::ZERO;
230 let transformed = transform.transform_point(point);
231
232 assert_eq!(transformed, Vector::new(10.0, 20.0, 30.0));
233 }
234
235 #[test]
236 fn test_transform_matrix_roundtrip() {
237 let original = Transform::new(
238 Vector::new(1.0, 2.0, 3.0),
239 Quaternion::from_rotation_y(45.0_f32.to_radians()),
240 Vector::new(2.0, 2.0, 2.0),
241 );
242
243 let matrix = original.to_matrix();
244 let reconstructed = Transform::from_matrix(matrix);
245
246 assert!(original.is_nearly_equal(reconstructed, 0.001));
247 }
248
249 #[test]
250 fn test_transform_combine() {
251 let t1 = Transform::from_location(Vector::new(10.0, 0.0, 0.0));
252 let t2 = Transform::from_location(Vector::new(0.0, 20.0, 0.0));
253
254 let combined = t1.combine(t2);
255 let point = Vector::ZERO;
256 let result = combined.transform_point(point);
257
258 assert_eq!(result, Vector::new(10.0, 20.0, 0.0));
259 }
260
261 #[test]
262 fn test_transform_inverse() {
263 let transform = Transform::new(
264 Vector::new(10.0, 20.0, 30.0),
265 Quaternion::from_rotation_z(90.0_f32.to_radians()),
266 Vector::splat(2.0),
267 );
268
269 let inverse = transform.inverse();
270 let combined = transform.combine(inverse);
271
272 assert!(combined.is_nearly_identity(0.001));
273 }
274
275 #[test]
276 fn test_transform_display() {
277 let transform = Transform::new(
278 Vector::new(10.0, 20.0, 30.0),
279 Quaternion::from_rotation_y(45.0_f32.to_radians()),
280 Vector::new(2.0, 2.0, 2.0),
281 );
282
283 let display_str = format!("{}", transform);
284 assert!(display_str.contains("Location: (10.00, 20.00, 30.00)"));
285 assert!(display_str.contains("Scale: (2.00, 2.00, 2.00)"));
286 }
287
288 #[test]
289 fn test_transform_json_serialization() {
290 let transform = Transform::from_location(Vector::new(1.0, 2.0, 3.0));
291
292 let json = serde_json::to_string(&transform).unwrap();
294 let deserialized: Transform = serde_json::from_str(&json).unwrap();
295
296 assert!(transform.is_nearly_equal(deserialized, 0.001));
297 }
298
299 #[test]
300 fn test_transform_binary_serialization() {
301 let transform = Transform::new(
302 Vector::new(10.0, 20.0, 30.0),
303 Quaternion::from_rotation_z(90.0_f32.to_radians()),
304 Vector::splat(2.0),
305 );
306
307 let binary = transform.to_binary().unwrap();
309 let deserialized = Transform::from_binary(&binary).unwrap();
310
311 assert!(transform.is_nearly_equal(deserialized, 0.001));
312 }
313}