Skip to main content

goud_engine/ecs/components/transform/
core.rs

1//! Core `Transform` struct and constructor methods.
2
3use crate::core::math::Matrix4;
4use crate::core::math::Vec3;
5use crate::ecs::components::transform::quat::Quat;
6use crate::ecs::Component;
7use cgmath::Quaternion;
8
9/// A 3D spatial transformation component.
10///
11/// Represents position, rotation, and scale in 3D space. This is the primary
12/// component for positioning entities in the game world.
13///
14/// # Memory Layout
15///
16/// The component is laid out as:
17/// - `position`: 3 x f32 (12 bytes)
18/// - `rotation`: 4 x f32 (16 bytes) - quaternion (x, y, z, w)
19/// - `scale`: 3 x f32 (12 bytes)
20/// - Total: 40 bytes
21///
22/// # Example
23///
24/// ```
25/// use goud_engine::ecs::components::Transform;
26/// use goud_engine::core::math::Vec3;
27///
28/// // Create at origin with default rotation and scale
29/// let mut transform = Transform::default();
30///
31/// // Or create with specific position
32/// let transform = Transform::from_position(Vec3::new(10.0, 0.0, 0.0));
33///
34/// // Or with full control
35/// let transform = Transform::new(
36///     Vec3::new(10.0, 5.0, 0.0),     // position
37///     Transform::IDENTITY_ROTATION,   // rotation (identity quaternion)
38///     Vec3::one(),                    // scale
39/// );
40/// ```
41#[repr(C)]
42#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
43pub struct Transform {
44    /// Position in world space (or local space if entity has a parent).
45    pub position: Vec3,
46    /// Rotation as a quaternion (x, y, z, w).
47    ///
48    /// The quaternion should be normalized. Use the rotation methods to ensure
49    /// this invariant is maintained.
50    pub rotation: Quat,
51    /// Scale along each axis.
52    ///
53    /// A scale of (1, 1, 1) means no scaling. Negative values flip the object
54    /// along that axis.
55    pub scale: Vec3,
56}
57
58impl Transform {
59    /// Identity rotation (no rotation).
60    pub const IDENTITY_ROTATION: Quat = Quat::IDENTITY;
61
62    /// Creates a new Transform with the specified position, rotation, and scale.
63    #[inline]
64    pub const fn new(position: Vec3, rotation: Quat, scale: Vec3) -> Self {
65        Self {
66            position,
67            rotation,
68            scale,
69        }
70    }
71
72    /// Creates a Transform at the specified position with default rotation and scale.
73    #[inline]
74    pub fn from_position(position: Vec3) -> Self {
75        Self {
76            position,
77            rotation: Quat::IDENTITY,
78            scale: Vec3::one(),
79        }
80    }
81
82    /// Creates a Transform with the specified rotation and default position/scale.
83    #[inline]
84    pub fn from_rotation(rotation: Quat) -> Self {
85        Self {
86            position: Vec3::zero(),
87            rotation,
88            scale: Vec3::one(),
89        }
90    }
91
92    /// Creates a Transform with the specified scale and default position/rotation.
93    #[inline]
94    pub fn from_scale(scale: Vec3) -> Self {
95        Self {
96            position: Vec3::zero(),
97            rotation: Quat::IDENTITY,
98            scale,
99        }
100    }
101
102    /// Creates a Transform with uniform scale.
103    #[inline]
104    pub fn from_scale_uniform(scale: f32) -> Self {
105        Self::from_scale(Vec3::new(scale, scale, scale))
106    }
107
108    /// Creates a Transform with position and rotation.
109    #[inline]
110    pub fn from_position_rotation(position: Vec3, rotation: Quat) -> Self {
111        Self {
112            position,
113            rotation,
114            scale: Vec3::one(),
115        }
116    }
117
118    /// Creates a Transform looking at a target position.
119    ///
120    /// # Arguments
121    ///
122    /// * `eye` - The position of the transform
123    /// * `target` - The point to look at
124    /// * `up` - The up direction (usually Vec3::unit_y())
125    ///
126    /// # Example
127    ///
128    /// ```
129    /// use goud_engine::ecs::components::Transform;
130    /// use goud_engine::core::math::Vec3;
131    ///
132    /// let transform = Transform::look_at(
133    ///     Vec3::new(0.0, 5.0, 10.0),  // eye position
134    ///     Vec3::zero(),               // looking at origin
135    ///     Vec3::unit_y(),             // up direction
136    /// );
137    /// ```
138    #[inline]
139    pub fn look_at(eye: Vec3, target: Vec3, up: Vec3) -> Self {
140        // Calculate the forward direction (from eye to target)
141        let forward = (target - eye).normalize();
142        let right = up.cross(forward).normalize();
143        let up = forward.cross(right);
144
145        // We want the rotation that transforms:
146        //   local -Z -> world forward
147        //   local +X -> world right
148        //   local +Y -> world up
149        //
150        // Since forward() returns rotate_vector(-Z), we need a rotation matrix
151        // where column 2 (Z basis) is -forward (negated), so that when we negate
152        // Z and rotate, we get forward.
153        //
154        // The rotation matrix M has columns [right, up, -forward] so that:
155        //   M * [0,0,-1] = forward
156        //   M * [1,0,0] = right
157        //   M * [0,1,0] = up
158        let neg_forward = Vec3::new(-forward.x, -forward.y, -forward.z);
159
160        // Build rotation matrix columns: [right, up, -forward]
161        let m00 = right.x;
162        let m01 = up.x;
163        let m02 = neg_forward.x;
164        let m10 = right.y;
165        let m11 = up.y;
166        let m12 = neg_forward.y;
167        let m20 = right.z;
168        let m21 = up.z;
169        let m22 = neg_forward.z;
170
171        let trace = m00 + m11 + m22;
172        let rotation = if trace > 0.0 {
173            let s = (trace + 1.0).sqrt() * 2.0;
174            Quat::new((m21 - m12) / s, (m02 - m20) / s, (m10 - m01) / s, 0.25 * s)
175        } else if m00 > m11 && m00 > m22 {
176            let s = (1.0 + m00 - m11 - m22).sqrt() * 2.0;
177            Quat::new(0.25 * s, (m01 + m10) / s, (m02 + m20) / s, (m21 - m12) / s)
178        } else if m11 > m22 {
179            let s = (1.0 + m11 - m00 - m22).sqrt() * 2.0;
180            Quat::new((m01 + m10) / s, 0.25 * s, (m12 + m21) / s, (m02 - m20) / s)
181        } else {
182            let s = (1.0 + m22 - m00 - m11).sqrt() * 2.0;
183            Quat::new((m02 + m20) / s, (m12 + m21) / s, 0.25 * s, (m10 - m01) / s)
184        };
185
186        Self {
187            position: eye,
188            rotation: rotation.normalize(),
189            scale: Vec3::one(),
190        }
191    }
192
193    /// Computes the 4x4 transformation matrix.
194    ///
195    /// The matrix represents the combined transformation: Scale * Rotation * Translation
196    /// (applied in that order when transforming points).
197    #[inline]
198    pub fn matrix(&self) -> Matrix4<f32> {
199        let rotation: Quaternion<f32> = self.rotation.into();
200        let rotation_matrix: cgmath::Matrix3<f32> = rotation.into();
201
202        // Build transformation matrix: T * R * S
203        // Scale
204        let sx = self.scale.x;
205        let sy = self.scale.y;
206        let sz = self.scale.z;
207
208        // Rotation columns scaled
209        let r = rotation_matrix;
210
211        Matrix4::new(
212            r.x.x * sx,
213            r.x.y * sx,
214            r.x.z * sx,
215            0.0,
216            r.y.x * sy,
217            r.y.y * sy,
218            r.y.z * sy,
219            0.0,
220            r.z.x * sz,
221            r.z.y * sz,
222            r.z.z * sz,
223            0.0,
224            self.position.x,
225            self.position.y,
226            self.position.z,
227            1.0,
228        )
229    }
230
231    /// Computes the inverse transformation matrix.
232    ///
233    /// Useful for view matrices or converting world-space to local-space.
234    #[inline]
235    pub fn matrix_inverse(&self) -> Matrix4<f32> {
236        // For a TRS matrix, inverse is: S^-1 * R^-1 * T^-1
237        let inv_rotation = self.rotation.inverse();
238        let inv_rotation_cg: Quaternion<f32> = inv_rotation.into();
239        let inv_rotation_matrix: cgmath::Matrix3<f32> = inv_rotation_cg.into();
240
241        let inv_scale = Vec3::new(1.0 / self.scale.x, 1.0 / self.scale.y, 1.0 / self.scale.z);
242
243        // Rotated and scaled inverse translation
244        let inv_pos = inv_rotation.rotate_vector(-self.position);
245        let inv_pos_scaled = Vec3::new(
246            inv_pos.x * inv_scale.x,
247            inv_pos.y * inv_scale.y,
248            inv_pos.z * inv_scale.z,
249        );
250
251        let r = inv_rotation_matrix;
252
253        Matrix4::new(
254            r.x.x * inv_scale.x,
255            r.x.y * inv_scale.x,
256            r.x.z * inv_scale.x,
257            0.0,
258            r.y.x * inv_scale.y,
259            r.y.y * inv_scale.y,
260            r.y.z * inv_scale.y,
261            0.0,
262            r.z.x * inv_scale.z,
263            r.z.y * inv_scale.z,
264            r.z.z * inv_scale.z,
265            0.0,
266            inv_pos_scaled.x,
267            inv_pos_scaled.y,
268            inv_pos_scaled.z,
269            1.0,
270        )
271    }
272}
273
274impl Default for Transform {
275    /// Returns a Transform at the origin with no rotation and unit scale.
276    #[inline]
277    fn default() -> Self {
278        Self {
279            position: Vec3::zero(),
280            rotation: Quat::IDENTITY,
281            scale: Vec3::one(),
282        }
283    }
284}
285
286// Implement Component trait for Transform
287impl Component for Transform {}