ue_types/
transform.rs

1//! Transform type for representing object transformations
2
3use crate::vector::*;
4use crate::rotator::*;
5use crate::BinarySerializable;
6use glam::Mat4;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10/// Transform containing Location, Rotation, and Scale
11/// 
12/// This represents a complete 3D transformation including:
13/// - Location: 3D position in world space
14/// - Rotation: 3D rotation as a quaternion
15/// - Scale: 3D scale factors
16#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
17pub struct Transform {
18    /// 3D position/location
19    pub location: Vector,
20    /// 3D rotation as quaternion
21    pub rotation: Quaternion,
22    /// 3D scale factors
23    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    /// Identity transform (no translation, rotation, or scaling)
42    pub const IDENTITY: Self = Self {
43        location: Vector::ZERO,
44        rotation: Quaternion::IDENTITY,
45        scale: Vector::ONE,
46    };
47
48    /// Create a new transform with the given location, rotation, and scale
49    pub fn new(location: Vector, rotation: Quaternion, scale: Vector) -> Self {
50        Self { location, rotation, scale }
51    }
52
53    /// Create a transform with only location (identity rotation and scale)
54    pub fn from_location(location: Vector) -> Self {
55        Self {
56            location,
57            ..Self::IDENTITY
58        }
59    }
60
61    /// Create a transform with only rotation (zero location, identity scale)
62    pub fn from_rotation(rotation: Quaternion) -> Self {
63        Self {
64            rotation,
65            ..Self::IDENTITY
66        }
67    }
68
69    /// Create a transform with only scale (zero location, identity rotation)
70    pub fn from_scale(scale: Vector) -> Self {
71        Self {
72            scale,
73            ..Self::IDENTITY
74        }
75    }
76
77    /// Create a transform with uniform scale
78    pub fn from_uniform_scale(scale: f32) -> Self {
79        Self {
80            scale: Vector::splat(scale),
81            ..Self::IDENTITY
82        }
83    }
84
85    /// Create a transform from location and rotator
86    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    /// Create a transform from location, rotator, and scale
95    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    /// Convert to 4x4 transformation matrix
104    pub fn to_matrix(self) -> Matrix4 {
105        Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.location)
106    }
107
108    /// Create transform from a 4x4 matrix
109    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    /// Get the rotator representation of the rotation
115    pub fn get_rotator(self) -> Rotator {
116        Rotator::from_quaternion(self.rotation)
117    }
118
119    /// Set rotation using a rotator
120    pub fn set_rotator(&mut self, rotator: Rotator) {
121        self.rotation = rotator.to_quaternion();
122    }
123
124    /// Transform a point by this transform (applies scale, rotation, and translation)
125    pub fn transform_point(self, point: Vector) -> Vector {
126        self.to_matrix().transform_point3(point)
127    }
128
129    /// Transform a vector by this transform (applies scale and rotation, ignores translation)
130    pub fn transform_vector(self, vector: Vector) -> Vector {
131        self.rotation * (vector * self.scale)
132    }
133
134    /// Transform a direction vector (applies only rotation, ignores scale and translation)
135    pub fn transform_direction(self, direction: Vector) -> Vector {
136        self.rotation * direction
137    }
138
139    /// Get the inverse of this transform
140    pub fn inverse(self) -> Self {
141        let inv_matrix = self.to_matrix().inverse();
142        Self::from_matrix(inv_matrix)
143    }
144
145    /// Combine this transform with another (this transform is applied first)
146    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    /// Get the forward vector for this transform
152    pub fn get_forward_vector(self) -> Vector {
153        self.transform_direction(VectorConstants::FORWARD)
154    }
155
156    /// Get the right vector for this transform
157    pub fn get_right_vector(self) -> Vector {
158        self.transform_direction(VectorConstants::RIGHT)
159    }
160
161    /// Get the up vector for this transform
162    pub fn get_up_vector(self) -> Vector {
163        self.transform_direction(VectorConstants::UP)
164    }
165
166    /// Check if this transform is nearly equal to another
167    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    /// Check if this transform is nearly the identity transform
174    pub fn is_nearly_identity(self, tolerance: f32) -> bool {
175        self.is_nearly_equal(Self::IDENTITY, tolerance)
176    }
177
178    /// Linearly interpolate between two transforms
179    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    /// Add translation to this transform
188    pub fn add_location(mut self, delta: Vector) -> Self {
189        self.location += delta;
190        self
191    }
192
193    /// Add rotation to this transform
194    pub fn add_rotation(mut self, delta_rotation: Quaternion) -> Self {
195        self.rotation = delta_rotation * self.rotation;
196        self
197    }
198
199    /// Add uniform scale to this transform
200    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        // Identity transform should not change the point
223        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        // Test JSON serialization
293        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        // Test binary serialization
308        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}