goud_engine/ecs/components/global_transform2d/operations.rs
1//! Transform operations, direction helpers, and interpolation for [`GlobalTransform2D`].
2
3use crate::core::math::Vec2;
4use crate::ecs::components::transform2d::Transform2D;
5use std::f32::consts::PI;
6
7use super::types::GlobalTransform2D;
8
9impl GlobalTransform2D {
10 /// Multiplies this transform with another.
11 ///
12 /// This combines two transformations: `self * other` applies `self` first,
13 /// then `other`.
14 ///
15 /// # Example
16 ///
17 /// ```
18 /// use goud_engine::ecs::components::GlobalTransform2D;
19 /// use goud_engine::core::math::Vec2;
20 ///
21 /// let parent = GlobalTransform2D::from_translation(Vec2::new(100.0, 0.0));
22 /// let child_local = GlobalTransform2D::from_translation(Vec2::new(50.0, 0.0));
23 ///
24 /// let child_global = parent.mul_transform(&child_local);
25 /// let pos = child_global.translation();
26 /// assert!((pos.x - 150.0).abs() < 0.001);
27 /// ```
28 #[inline]
29 pub fn mul_transform(&self, other: &GlobalTransform2D) -> GlobalTransform2D {
30 GlobalTransform2D {
31 matrix: self.matrix.multiply(&other.matrix),
32 }
33 }
34
35 /// Multiplies this transform by a local Transform2D.
36 ///
37 /// This is the primary method used by 2D transform propagation.
38 ///
39 /// # Example
40 ///
41 /// ```
42 /// use goud_engine::ecs::components::{GlobalTransform2D, Transform2D};
43 /// use goud_engine::core::math::Vec2;
44 ///
45 /// let parent_global = GlobalTransform2D::from_translation(Vec2::new(100.0, 0.0));
46 /// let child_local = Transform2D::from_position(Vec2::new(50.0, 0.0));
47 ///
48 /// let child_global = parent_global.transform_by(&child_local);
49 /// let pos = child_global.translation();
50 /// assert!((pos.x - 150.0).abs() < 0.001);
51 /// ```
52 #[inline]
53 pub fn transform_by(&self, local: &Transform2D) -> GlobalTransform2D {
54 GlobalTransform2D {
55 matrix: self.matrix.multiply(&local.matrix()),
56 }
57 }
58
59 /// Transforms a point from local space to world space.
60 ///
61 /// # Example
62 ///
63 /// ```
64 /// use goud_engine::ecs::components::GlobalTransform2D;
65 /// use goud_engine::core::math::Vec2;
66 ///
67 /// let global = GlobalTransform2D::from_translation(Vec2::new(100.0, 0.0));
68 /// let local_point = Vec2::new(50.0, 0.0);
69 /// let world_point = global.transform_point(local_point);
70 ///
71 /// assert!((world_point.x - 150.0).abs() < 0.001);
72 /// ```
73 #[inline]
74 pub fn transform_point(&self, point: Vec2) -> Vec2 {
75 self.matrix.transform_point(point)
76 }
77
78 /// Transforms a direction from local space to world space.
79 ///
80 /// Unlike points, directions are not affected by translation.
81 #[inline]
82 pub fn transform_direction(&self, direction: Vec2) -> Vec2 {
83 self.matrix.transform_direction(direction)
84 }
85
86 /// Returns the inverse of this transform.
87 ///
88 /// The inverse transforms from world space back to local space.
89 /// Returns `None` if the matrix is not invertible (e.g., has zero scale).
90 #[inline]
91 pub fn inverse(&self) -> Option<GlobalTransform2D> {
92 self.matrix
93 .inverse()
94 .map(|m| GlobalTransform2D { matrix: m })
95 }
96
97 // =========================================================================
98 // Direction Vectors
99 // =========================================================================
100
101 /// Returns the forward direction vector (positive Y in local space for 2D).
102 ///
103 /// Note: In 2D, "forward" is typically the positive Y direction.
104 #[inline]
105 pub fn forward(&self) -> Vec2 {
106 self.transform_direction(Vec2::new(0.0, 1.0)).normalize()
107 }
108
109 /// Returns the right direction vector (positive X in local space).
110 #[inline]
111 pub fn right(&self) -> Vec2 {
112 self.transform_direction(Vec2::new(1.0, 0.0)).normalize()
113 }
114
115 /// Returns the backward direction vector (negative Y in local space).
116 #[inline]
117 pub fn backward(&self) -> Vec2 {
118 self.transform_direction(Vec2::new(0.0, -1.0)).normalize()
119 }
120
121 /// Returns the left direction vector (negative X in local space).
122 #[inline]
123 pub fn left(&self) -> Vec2 {
124 self.transform_direction(Vec2::new(-1.0, 0.0)).normalize()
125 }
126
127 // =========================================================================
128 // Interpolation
129 // =========================================================================
130
131 /// Linearly interpolates between two global transforms.
132 ///
133 /// This decomposes both transforms, interpolates components separately
134 /// (lerp for angle), then recomposes.
135 ///
136 /// # Example
137 ///
138 /// ```
139 /// use goud_engine::ecs::components::GlobalTransform2D;
140 /// use goud_engine::core::math::Vec2;
141 ///
142 /// let a = GlobalTransform2D::from_translation(Vec2::zero());
143 /// let b = GlobalTransform2D::from_translation(Vec2::new(100.0, 0.0));
144 ///
145 /// let mid = a.lerp(&b, 0.5);
146 /// let pos = mid.translation();
147 /// assert!((pos.x - 50.0).abs() < 0.001);
148 /// ```
149 #[inline]
150 pub fn lerp(&self, other: &GlobalTransform2D, t: f32) -> GlobalTransform2D {
151 let (t1, r1, s1) = self.decompose();
152 let (t2, r2, s2) = other.decompose();
153
154 GlobalTransform2D::from_translation_rotation_scale(
155 t1.lerp(t2, t),
156 lerp_angle(r1, r2, t),
157 s1.lerp(s2, t),
158 )
159 }
160}
161
162/// Linearly interpolates between two angles, taking the shortest path.
163#[inline]
164pub(crate) fn lerp_angle(a: f32, b: f32, t: f32) -> f32 {
165 let mut diff = b - a;
166
167 // Normalize to [-PI, PI]
168 while diff > PI {
169 diff -= 2.0 * PI;
170 }
171 while diff < -PI {
172 diff += 2.0 * PI;
173 }
174
175 a + diff * t
176}