Skip to main content

goud_engine/ecs/components/global_transform/
decomposition.rs

1//! Decomposition methods for [`GlobalTransform`].
2//!
3//! These methods extract translation, rotation, and scale from the underlying
4//! transformation matrix stored in a [`GlobalTransform`].
5
6use crate::core::math::Vec3;
7use crate::ecs::components::global_transform::core::GlobalTransform;
8use crate::ecs::components::transform::{Quat, Transform};
9use cgmath::InnerSpace;
10
11impl GlobalTransform {
12    // =========================================================================
13    // Decomposition Methods
14    // =========================================================================
15
16    /// Extracts the translation (position) component.
17    ///
18    /// This is the fourth column of the matrix.
19    ///
20    /// # Example
21    ///
22    /// ```
23    /// use goud_engine::ecs::components::GlobalTransform;
24    /// use goud_engine::core::math::Vec3;
25    ///
26    /// let global = GlobalTransform::from_translation(Vec3::new(10.0, 5.0, 3.0));
27    /// let pos = global.translation();
28    ///
29    /// assert!((pos.x - 10.0).abs() < 0.001);
30    /// ```
31    #[inline]
32    pub fn translation(&self) -> Vec3 {
33        Vec3::new(self.matrix.w.x, self.matrix.w.y, self.matrix.w.z)
34    }
35
36    /// Extracts the scale component.
37    ///
38    /// This is computed from the lengths of the first three column vectors.
39    /// Note: This does not handle negative scales correctly.
40    ///
41    /// # Example
42    ///
43    /// ```
44    /// use goud_engine::ecs::components::GlobalTransform;
45    /// use goud_engine::ecs::components::transform::Quat;
46    /// use goud_engine::core::math::Vec3;
47    ///
48    /// let global = GlobalTransform::from_translation_rotation_scale(
49    ///     Vec3::zero(),
50    ///     Quat::IDENTITY,
51    ///     Vec3::new(2.0, 3.0, 4.0),
52    /// );
53    ///
54    /// let scale = global.scale();
55    /// assert!((scale.x - 2.0).abs() < 0.001);
56    /// ```
57    #[inline]
58    pub fn scale(&self) -> Vec3 {
59        let scale_x =
60            cgmath::Vector3::new(self.matrix.x.x, self.matrix.x.y, self.matrix.x.z).magnitude();
61        let scale_y =
62            cgmath::Vector3::new(self.matrix.y.x, self.matrix.y.y, self.matrix.y.z).magnitude();
63        let scale_z =
64            cgmath::Vector3::new(self.matrix.z.x, self.matrix.z.y, self.matrix.z.z).magnitude();
65        Vec3::new(scale_x, scale_y, scale_z)
66    }
67
68    /// Extracts the rotation component as a quaternion.
69    ///
70    /// This removes scale from the rotation matrix, then converts to quaternion.
71    /// Note: This may have issues with non-uniform scales or negative scales.
72    ///
73    /// # Example
74    ///
75    /// ```
76    /// use goud_engine::ecs::components::GlobalTransform;
77    /// use goud_engine::ecs::components::transform::Quat;
78    /// use goud_engine::core::math::Vec3;
79    /// use std::f32::consts::PI;
80    ///
81    /// let rotation = Quat::from_axis_angle(Vec3::unit_y(), PI / 4.0);
82    /// let global = GlobalTransform::from_translation_rotation_scale(
83    ///     Vec3::zero(),
84    ///     rotation,
85    ///     Vec3::one(),
86    /// );
87    ///
88    /// let extracted = global.rotation();
89    /// // Quaternion comparison (accounting for sign flip equivalence)
90    /// let dot = rotation.x * extracted.x + rotation.y * extracted.y
91    ///         + rotation.z * extracted.z + rotation.w * extracted.w;
92    /// assert!(dot.abs() > 0.999);
93    /// ```
94    #[inline]
95    pub fn rotation(&self) -> Quat {
96        // Extract scale
97        let scale = self.scale();
98
99        // Build rotation matrix by normalizing columns
100        let m00 = if scale.x != 0.0 {
101            self.matrix.x.x / scale.x
102        } else {
103            1.0
104        };
105        let m01 = if scale.y != 0.0 {
106            self.matrix.y.x / scale.y
107        } else {
108            0.0
109        };
110        let m02 = if scale.z != 0.0 {
111            self.matrix.z.x / scale.z
112        } else {
113            0.0
114        };
115
116        let m10 = if scale.x != 0.0 {
117            self.matrix.x.y / scale.x
118        } else {
119            0.0
120        };
121        let m11 = if scale.y != 0.0 {
122            self.matrix.y.y / scale.y
123        } else {
124            1.0
125        };
126        let m12 = if scale.z != 0.0 {
127            self.matrix.z.y / scale.z
128        } else {
129            0.0
130        };
131
132        let m20 = if scale.x != 0.0 {
133            self.matrix.x.z / scale.x
134        } else {
135            0.0
136        };
137        let m21 = if scale.y != 0.0 {
138            self.matrix.y.z / scale.y
139        } else {
140            0.0
141        };
142        let m22 = if scale.z != 0.0 {
143            self.matrix.z.z / scale.z
144        } else {
145            1.0
146        };
147
148        // Convert rotation matrix to quaternion
149        let trace = m00 + m11 + m22;
150        if trace > 0.0 {
151            let s = (trace + 1.0).sqrt() * 2.0;
152            Quat::new((m21 - m12) / s, (m02 - m20) / s, (m10 - m01) / s, 0.25 * s).normalize()
153        } else if m00 > m11 && m00 > m22 {
154            let s = (1.0 + m00 - m11 - m22).sqrt() * 2.0;
155            Quat::new(0.25 * s, (m01 + m10) / s, (m02 + m20) / s, (m21 - m12) / s).normalize()
156        } else if m11 > m22 {
157            let s = (1.0 + m11 - m00 - m22).sqrt() * 2.0;
158            Quat::new((m01 + m10) / s, 0.25 * s, (m12 + m21) / s, (m02 - m20) / s).normalize()
159        } else {
160            let s = (1.0 + m22 - m00 - m11).sqrt() * 2.0;
161            Quat::new((m02 + m20) / s, (m12 + m21) / s, 0.25 * s, (m10 - m01) / s).normalize()
162        }
163    }
164
165    /// Decomposes the transform into translation, rotation, and scale.
166    ///
167    /// Returns `(translation, rotation, scale)`.
168    ///
169    /// # Example
170    ///
171    /// ```
172    /// use goud_engine::ecs::components::GlobalTransform;
173    /// use goud_engine::ecs::components::transform::Quat;
174    /// use goud_engine::core::math::Vec3;
175    ///
176    /// let global = GlobalTransform::from_translation_rotation_scale(
177    ///     Vec3::new(10.0, 5.0, 3.0),
178    ///     Quat::IDENTITY,
179    ///     Vec3::new(2.0, 2.0, 2.0),
180    /// );
181    ///
182    /// let (translation, rotation, scale) = global.decompose();
183    /// assert!((translation.x - 10.0).abs() < 0.001);
184    /// assert!((scale.x - 2.0).abs() < 0.001);
185    /// ```
186    #[inline]
187    pub fn decompose(&self) -> (Vec3, Quat, Vec3) {
188        (self.translation(), self.rotation(), self.scale())
189    }
190
191    /// Converts this GlobalTransform to a local Transform.
192    ///
193    /// This is useful for extracting a Transform that would produce this
194    /// GlobalTransform when applied from the origin.
195    #[inline]
196    pub fn to_transform(&self) -> Transform {
197        let (translation, rotation, scale) = self.decompose();
198        Transform::new(translation, rotation, scale)
199    }
200}