goud_engine/ecs/components/global_transform/operations.rs
1//! Transform operations, direction helpers, interpolation, and trait
2//! implementations for [`GlobalTransform`].
3
4use crate::core::math::{Matrix4, Vec3};
5use crate::ecs::components::global_transform::core::GlobalTransform;
6use crate::ecs::components::transform::Transform;
7use crate::ecs::Component;
8use cgmath::SquareMatrix;
9use std::fmt;
10
11impl GlobalTransform {
12 // =========================================================================
13 // Transform Operations
14 // =========================================================================
15
16 /// Multiplies this transform with another.
17 ///
18 /// This combines two transformations: `self * other` applies `self` first,
19 /// then `other`.
20 ///
21 /// # Example
22 ///
23 /// ```
24 /// use goud_engine::ecs::components::GlobalTransform;
25 /// use goud_engine::core::math::Vec3;
26 ///
27 /// let parent = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
28 /// let child_local = GlobalTransform::from_translation(Vec3::new(5.0, 0.0, 0.0));
29 ///
30 /// let child_global = parent.mul_transform(&child_local);
31 /// let pos = child_global.translation();
32 /// assert!((pos.x - 15.0).abs() < 0.001);
33 /// ```
34 #[inline]
35 pub fn mul_transform(&self, other: &GlobalTransform) -> GlobalTransform {
36 GlobalTransform {
37 matrix: self.matrix * other.matrix,
38 }
39 }
40
41 /// Multiplies this transform by a local Transform.
42 ///
43 /// This is the primary method used by transform propagation.
44 ///
45 /// # Example
46 ///
47 /// ```
48 /// use goud_engine::ecs::components::{GlobalTransform, Transform};
49 /// use goud_engine::core::math::Vec3;
50 ///
51 /// let parent_global = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
52 /// let child_local = Transform::from_position(Vec3::new(5.0, 0.0, 0.0));
53 ///
54 /// let child_global = parent_global.transform_by(&child_local);
55 /// let pos = child_global.translation();
56 /// assert!((pos.x - 15.0).abs() < 0.001);
57 /// ```
58 #[inline]
59 pub fn transform_by(&self, local: &Transform) -> GlobalTransform {
60 GlobalTransform {
61 matrix: self.matrix * local.matrix(),
62 }
63 }
64
65 /// Transforms a point from local space to world space.
66 ///
67 /// # Example
68 ///
69 /// ```
70 /// use goud_engine::ecs::components::GlobalTransform;
71 /// use goud_engine::core::math::Vec3;
72 ///
73 /// let global = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
74 /// let local_point = Vec3::new(5.0, 0.0, 0.0);
75 /// let world_point = global.transform_point(local_point);
76 ///
77 /// assert!((world_point.x - 15.0).abs() < 0.001);
78 /// ```
79 #[inline]
80 pub fn transform_point(&self, point: Vec3) -> Vec3 {
81 let p = cgmath::Vector4::new(point.x, point.y, point.z, 1.0);
82 let result = self.matrix * p;
83 Vec3::new(result.x, result.y, result.z)
84 }
85
86 /// Transforms a direction from local space to world space.
87 ///
88 /// Unlike points, directions are not affected by translation.
89 #[inline]
90 pub fn transform_direction(&self, direction: Vec3) -> Vec3 {
91 let d = cgmath::Vector4::new(direction.x, direction.y, direction.z, 0.0);
92 let result = self.matrix * d;
93 Vec3::new(result.x, result.y, result.z)
94 }
95
96 /// Returns the inverse of this transform.
97 ///
98 /// The inverse transforms from world space back to local space.
99 /// Returns `None` if the matrix is not invertible (e.g., has zero scale).
100 #[inline]
101 pub fn inverse(&self) -> Option<GlobalTransform> {
102 self.matrix.invert().map(|m| GlobalTransform { matrix: m })
103 }
104
105 // =========================================================================
106 // Direction Vectors
107 // =========================================================================
108
109 /// Returns the forward direction vector (negative Z in local space).
110 #[inline]
111 pub fn forward(&self) -> Vec3 {
112 self.transform_direction(Vec3::new(0.0, 0.0, -1.0))
113 .normalize()
114 }
115
116 /// Returns the right direction vector (positive X in local space).
117 #[inline]
118 pub fn right(&self) -> Vec3 {
119 self.transform_direction(Vec3::new(1.0, 0.0, 0.0))
120 .normalize()
121 }
122
123 /// Returns the up direction vector (positive Y in local space).
124 #[inline]
125 pub fn up(&self) -> Vec3 {
126 self.transform_direction(Vec3::new(0.0, 1.0, 0.0))
127 .normalize()
128 }
129
130 /// Returns the back direction vector (positive Z in local space).
131 #[inline]
132 pub fn back(&self) -> Vec3 {
133 self.transform_direction(Vec3::new(0.0, 0.0, 1.0))
134 .normalize()
135 }
136
137 /// Returns the left direction vector (negative X in local space).
138 #[inline]
139 pub fn left(&self) -> Vec3 {
140 self.transform_direction(Vec3::new(-1.0, 0.0, 0.0))
141 .normalize()
142 }
143
144 /// Returns the down direction vector (negative Y in local space).
145 #[inline]
146 pub fn down(&self) -> Vec3 {
147 self.transform_direction(Vec3::new(0.0, -1.0, 0.0))
148 .normalize()
149 }
150
151 // =========================================================================
152 // Interpolation
153 // =========================================================================
154
155 /// Linearly interpolates between two global transforms.
156 ///
157 /// This decomposes both transforms, interpolates components separately
158 /// (slerp for rotation), then recomposes.
159 ///
160 /// # Example
161 ///
162 /// ```
163 /// use goud_engine::ecs::components::GlobalTransform;
164 /// use goud_engine::core::math::Vec3;
165 ///
166 /// let a = GlobalTransform::from_translation(Vec3::zero());
167 /// let b = GlobalTransform::from_translation(Vec3::new(10.0, 0.0, 0.0));
168 ///
169 /// let mid = a.lerp(&b, 0.5);
170 /// let pos = mid.translation();
171 /// assert!((pos.x - 5.0).abs() < 0.001);
172 /// ```
173 #[inline]
174 pub fn lerp(&self, other: &GlobalTransform, t: f32) -> GlobalTransform {
175 let (t1, r1, s1) = self.decompose();
176 let (t2, r2, s2) = other.decompose();
177
178 GlobalTransform::from_translation_rotation_scale(
179 t1.lerp(t2, t),
180 r1.slerp(r2, t),
181 s1.lerp(s2, t),
182 )
183 }
184}
185
186impl Default for GlobalTransform {
187 #[inline]
188 fn default() -> Self {
189 Self::IDENTITY
190 }
191}
192
193impl fmt::Debug for GlobalTransform {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 let (t, r, s) = self.decompose();
196 f.debug_struct("GlobalTransform")
197 .field("translation", &(t.x, t.y, t.z))
198 .field(
199 "rotation",
200 &format!("Quat({:.3}, {:.3}, {:.3}, {:.3})", r.x, r.y, r.z, r.w),
201 )
202 .field("scale", &(s.x, s.y, s.z))
203 .finish()
204 }
205}
206
207impl From<Transform> for GlobalTransform {
208 /// Converts a local Transform to a GlobalTransform.
209 ///
210 /// This is used for root entities where local == global.
211 #[inline]
212 fn from(transform: Transform) -> Self {
213 GlobalTransform {
214 matrix: transform.matrix(),
215 }
216 }
217}
218
219impl From<&Transform> for GlobalTransform {
220 #[inline]
221 fn from(transform: &Transform) -> Self {
222 GlobalTransform {
223 matrix: transform.matrix(),
224 }
225 }
226}
227
228impl From<Matrix4<f32>> for GlobalTransform {
229 #[inline]
230 fn from(matrix: Matrix4<f32>) -> Self {
231 GlobalTransform { matrix }
232 }
233}
234
235impl Component for GlobalTransform {}
236
237impl std::ops::Mul for GlobalTransform {
238 type Output = GlobalTransform;
239
240 #[inline]
241 fn mul(self, rhs: GlobalTransform) -> GlobalTransform {
242 self.mul_transform(&rhs)
243 }
244}
245
246impl std::ops::Mul<&GlobalTransform> for GlobalTransform {
247 type Output = GlobalTransform;
248
249 #[inline]
250 fn mul(self, rhs: &GlobalTransform) -> GlobalTransform {
251 self.mul_transform(rhs)
252 }
253}
254
255impl std::ops::Mul<Transform> for GlobalTransform {
256 type Output = GlobalTransform;
257
258 #[inline]
259 fn mul(self, rhs: Transform) -> GlobalTransform {
260 self.transform_by(&rhs)
261 }
262}
263
264impl std::ops::Mul<&Transform> for GlobalTransform {
265 type Output = GlobalTransform;
266
267 #[inline]
268 fn mul(self, rhs: &Transform) -> GlobalTransform {
269 self.transform_by(rhs)
270 }
271}