Skip to main content

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}