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}