Skip to main content

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}