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 {}