bevy_mod_transform2d/
transform2d.rs

1use bevy::{math::Affine2, prelude::*};
2
3/// Describes the position of an [`Entity`] in 2d space.
4///
5/// This component acts as a proxy to the [`Transform`] component,
6/// and thus *requires* that both a [`Transform`] and [`GlobalTransform`] are present to function.
7///
8/// If this [`Transform2d`] has a [`Parent`], then it's relative to the [`Transform2d`] or [`Transform`] of the [`Parent`].
9#[derive(Component, Debug, PartialEq, Clone, Copy, Reflect)]
10#[reflect(Component, PartialEq, Default)]
11pub struct Transform2d {
12    /// The translation along the `X` and `Y` axes.
13    pub translation: Vec2,
14    /// The rotation in radians. Positive values rotate anti-clockwise.
15    pub rotation: f32,
16    /// The scale along the `X` and `Y` axes.
17    pub scale: Vec2,
18    /// The translation along the `Z` axis.
19    ///
20    /// You might be surprised that 2D entities have a translation along the Z axis,
21    /// but this third dimension is used when rendering to decide what should appear in front or behind.
22    /// A higher translation on the Z axis puts the entity closer to the camera, and thus in front of entities with a lower Z translation.
23    ///
24    /// Keep in mind that this is relative to the [`Parent`]'s `z_translation`.
25    /// The other fields on [`Transform2d`] don't affect this because they are strictly 2D.
26    pub z_translation: f32,
27}
28
29impl Default for Transform2d {
30    fn default() -> Self {
31        Transform2d::IDENTITY
32    }
33}
34
35impl Transform2d {
36    /// Creates a new identity [`Transform2d`], with no translation, rotation, and a scale of 1 on all axes.
37    ///
38    /// Translation is `Vec2::ZERO`, rotation is `0.`, scale is `Vec2::ONE` and `z_translation` is `0.`.
39    pub const IDENTITY: Self = Transform2d {
40        translation: Vec2::ZERO,
41        rotation: 0.,
42        scale: Vec2::ONE,
43        z_translation: 0.,
44    };
45
46    /// Creates a new [`Transform2d`] at the position `(x, y)`.
47    ///
48    /// Rotation will be `0.`, scale will be `Vec2::ONE` and `z_translation` will be `0.`.
49    #[inline]
50    pub fn from_xy(x: f32, y: f32) -> Self {
51        Transform2d::from_translation(Vec2::new(x, y))
52    }
53
54    /// Creates a new [`Transform2d`] at the position `(x, y, z)`. The `z` component
55    /// is used for z-ordering elements: higher `z`-value will be in front of lower
56    /// `z`-value.
57    #[inline]
58    pub fn from_xyz(x: f32, y: f32, z: f32) -> Self {
59        Self::from_translation(Vec2::new(x, y)).with_z_translation(z)
60    }
61
62    /// Creates a new [`Transform2d`] with `translation`.
63    ///
64    /// Rotation will be `0.`, scale will be `Vec2::ONE` and `z_translation` will be `0.`.
65    #[inline]
66    pub fn from_translation(translation: Vec2) -> Self {
67        Transform2d {
68            translation,
69            ..Self::IDENTITY
70        }
71    }
72
73    /// Creates a new [`Transform2d`] with `translation`.
74    ///
75    /// Rotation will be `0.` and scale will be `Vec2::ONE`
76    #[inline]
77    pub fn from_translation_3d(Vec3 { x, y, z }: Vec3) -> Self {
78        Transform2d {
79            translation: Vec2 { x, y },
80            z_translation: z,
81            ..Self::IDENTITY
82        }
83    }
84
85    /// Creates a new [`Transform2d`] with `rotation`.
86    ///
87    /// Translation will be `Vec2::ZERO`, scale will be `Vec2::ONE` and `z_translation` will be `0.`.
88    #[inline]
89    pub fn from_rotation(rotation: f32) -> Self {
90        Transform2d {
91            rotation,
92            ..Self::IDENTITY
93        }
94    }
95
96    /// Creates a new [`Transform2d`] with `scale`.
97    ///
98    /// Translation will be `Vec2::ZERO`, rotation will be `0.` and `z_translation` will be `0.`
99    #[inline] // Hmm
100    pub fn from_scale(scale: impl IntoScale) -> Self {
101        Transform2d {
102            scale: scale.into_scale(),
103            ..Self::IDENTITY
104        }
105    }
106
107    /// Returns this [`Transform2d`] with a new translation.
108    #[must_use]
109    #[inline]
110    pub fn with_translation(mut self, translation: Vec2) -> Self {
111        self.translation = translation;
112        self
113    }
114
115    /// Returns this [`Transform2d`] with a new rotation.
116    #[must_use]
117    #[inline]
118    pub fn with_rotation(mut self, rotation: f32) -> Self {
119        self.rotation = rotation;
120        self
121    }
122
123    /// Returns this [`Transform2d`] with a new scale.
124    #[must_use]
125    #[inline]
126    pub fn with_scale(mut self, scale: Vec2) -> Self {
127        self.scale = scale;
128        self
129    }
130
131    /// Returns this [`Transform2d`] with a new Z translation.
132    #[must_use]
133    #[inline]
134    pub fn with_z_translation(mut self, z_translation: f32) -> Self {
135        self.z_translation = z_translation;
136        self
137    }
138
139    /// Returns this [`Transform2d`] rotated so the local `direction` points in the given `target_direction`.
140    ///
141    /// # Example
142    /// ```
143    /// # use bevy::prelude::*;
144    /// # use bevy_mod_transform2d::prelude::*;
145    /// // Create a transform rotated so that up/local_y points to the right.
146    /// let transform = Transform2d::IDENTITY.pointed_to(Vec2::Y, Vec2::X);
147    ///
148    /// approx::assert_abs_diff_eq!(transform.up(), Vec2::X);
149    /// ```
150    ///
151    /// If this [`Transform2d`] has a parent, the `point` is relative to the [`Transform2d`] of the parent.
152    #[inline]
153    pub fn pointed_to(mut self, direction: Vec2, target_direction: Vec2) -> Self {
154        self.point_to(direction, target_direction);
155        self
156    }
157
158    /// Returns this [`Transform2d`] rotated so the local `direction` points at the given `target_position`.
159    ///
160    /// # Example
161    /// ```
162    /// # use bevy::prelude::*;
163    /// # use bevy_mod_transform2d::prelude::*;
164    /// // Create a transform that is translated to Vec2::ONE and then rotated so that up/local_y points to the origin.
165    /// let transform = Transform2d::from_translation(Vec2::ONE)
166    ///     .pointed_at(Vec2::Y, Vec2::ZERO);
167    ///
168    /// approx::assert_abs_diff_eq!(transform.up(), Vec2::NEG_ONE.normalize());
169    /// ```
170    ///
171    /// If this [`Transform2d`] has a parent, the `point` is relative to the [`Transform2d`] of the parent.
172    #[inline]
173    pub fn pointed_at(mut self, direction: Vec2, target_position: Vec2) -> Self {
174        self.point_at(direction, target_position);
175        self
176    }
177
178    /// Rotates this [`Transform2d`] so the local `direction` points in the given `target_direction`.
179    ///
180    /// # Example
181    /// ```
182    /// # use bevy::prelude::*;
183    /// # use bevy_mod_transform2d::prelude::*;
184    /// let mut transform = Transform2d::IDENTITY;
185    ///
186    /// // Rotate the transform so that up/local_y points to the right.
187    /// transform.point_to(Vec2::Y, Vec2::X);
188    ///
189    /// approx::assert_abs_diff_eq!(transform.up(), Vec2::X);
190    /// ```
191    ///
192    /// If this [`Transform2d`] has a parent, the `point` is relative to the [`Transform2d`] of the parent.
193    #[inline]
194    pub fn point_to(&mut self, direction: Vec2, target_direction: Vec2) {
195        self.rotation = Vec2::angle_between(direction, target_direction);
196    }
197
198    /// Rotates this [`Transform2d`] so the local `direction` points at the given `target_position`.
199    ///
200    /// # Example
201    /// ```
202    /// # use bevy::prelude::*;
203    /// # use bevy_mod_transform2d::prelude::*;
204    /// let mut transform = Transform2d::from_translation(Vec2::ONE);
205    ///
206    /// // Rotate the transform so that up/local_y points to the origin.
207    /// transform.point_at(Vec2::Y, Vec2::ZERO);
208    ///
209    /// approx::assert_abs_diff_eq!(transform.up(), Vec2::NEG_ONE.normalize());
210    /// ```
211    ///
212    /// If this [`Transform2d`] has a parent, the `point` is relative to the [`Transform2d`] of the parent.
213    #[inline]
214    pub fn point_at(&mut self, direction: Vec2, target_position: Vec2) {
215        self.point_to(direction, target_position - self.translation);
216    }
217
218    /// Get the unit vector in the local `X` direction.
219    #[inline]
220    pub fn local_x(&self) -> Vec2 {
221        let (sin, cos) = self.rotation.sin_cos();
222        (cos, sin).into()
223    }
224
225    #[inline]
226    /// Equivalent to [`-local_x()`][Self::local_x()]
227    pub fn left(&self) -> Vec2 {
228        -self.local_x()
229    }
230
231    #[inline]
232    /// Equivalent to [`local_x()`][Self::local_x()]
233    pub fn right(&self) -> Vec2 {
234        self.local_x()
235    }
236
237    /// Get the unit vector in the local `Y` direction.
238    #[inline]
239    pub fn local_y(&self) -> Vec2 {
240        let (sin, cos) = self.rotation.sin_cos();
241        (-sin, cos).into()
242    }
243
244    /// Equivalent to [`local_y()`][Self::local_y]
245    #[inline]
246    pub fn up(&self) -> Vec2 {
247        self.local_y()
248    }
249
250    /// Equivalent to [`-local_y()`][Self::local_y]
251    #[inline]
252    pub fn down(&self) -> Vec2 {
253        -self.local_y()
254    }
255
256    /// Returns the rotation matrix from this transforms rotation.
257    #[inline]
258    pub fn rotation_matrix(&self) -> Mat2 {
259        Mat2::from_angle(self.rotation)
260    }
261
262    /// Computes the affine transformation matrix of this transform.
263    #[inline]
264    pub fn compute_matrix(&self) -> Mat3 {
265        Mat3::from_scale_angle_translation(self.scale, self.rotation, self.translation)
266    }
267
268    /// Computes the affine transform of this transform.
269    #[inline]
270    pub fn compute_affine(&self) -> Affine2 {
271        Affine2::from_scale_angle_translation(self.scale, self.rotation, self.translation)
272    }
273
274    /// Translates this [`Transform2d`] around a `point` in space.
275    ///
276    /// If this [`Transform2d`] has a parent, the `point` is relative to the [`Transform2d`] or [`Transform`] of the parent.
277    #[inline]
278    pub fn translate_around(&mut self, point: Vec2, angle: f32) {
279        self.translation = point + Mat2::from_angle(angle) * (self.translation - point);
280    }
281
282    /// Rotates this [`Transform2d`] around a `point` in space.
283    ///
284    /// If this [`Transform2d`] has a parent, the `point` is relative to the [`Transform2d`] or [`Transform`] of the parent.
285    #[inline]
286    pub fn rotate_around(&mut self, point: Vec2, angle: f32) {
287        self.translate_around(point, angle);
288        self.rotation += angle;
289    }
290
291    /// Transforms the given `point`, applying scale, rotation and translation.
292    /// `z_translation` is ignored.
293    ///
294    /// If this [`Transform2d`] has a parent, this will transform a `point` that is
295    /// relative to the parent's [`Transform2d`] into one relative to this [`Transform2d`].
296    ///
297    /// If this [`Transform2d`] does not have a parent, this will transform a `point`
298    /// that is in global space into one relative to this [`Transform2d`].
299    ///
300    /// If you want to transform a `point` in global space to the local space of this [`Transform2d`],
301    /// consider using [`GlobalTransform2d::transform_point()`](super::GlobalTransform2d::transform_point) instead.
302    #[inline]
303    pub fn transform_point(&self, mut point: Vec2) -> Vec2 {
304        point *= self.scale;
305        point = self.rotation_matrix() * point;
306        point += self.translation;
307        point
308    }
309
310    /// Multiplies `self` with `transform` component by component, returning the
311    /// resulting [`Transform2d`]
312    #[inline]
313    #[must_use]
314    pub fn mul_transform(&self, transform: Transform2d) -> Self {
315        let translation = self.transform_point(transform.translation);
316        let rotation = self.rotation + transform.rotation;
317        let scale = self.scale * transform.scale;
318        let z_translation = self.z_translation + transform.z_translation;
319        Transform2d {
320            translation,
321            rotation,
322            scale,
323            z_translation,
324        }
325    }
326}
327
328impl From<Transform2d> for Transform {
329    #[inline]
330    fn from(transform2d: Transform2d) -> Self {
331        Transform {
332            translation: transform2d.translation.extend(transform2d.z_translation),
333            rotation: Quat::from_rotation_z(transform2d.rotation),
334            scale: transform2d.scale.extend(1.),
335        }
336    }
337}
338
339impl From<Transform> for Transform2d {
340    fn from(transform_3d: Transform) -> Self {
341        Transform2d {
342            translation: transform_3d.translation.truncate(),
343            rotation: transform_3d.rotation.to_euler(EulerRot::ZYX).0,
344            scale: transform_3d.scale.truncate(),
345            z_translation: transform_3d.translation.z,
346        }
347    }
348}
349
350pub trait IntoScale {
351    fn into_scale(self) -> Vec2;
352}
353
354impl IntoScale for Vec2 {
355    fn into_scale(self) -> Vec2 {
356        self
357    }
358}
359
360impl IntoScale for f32 {
361    fn into_scale(self) -> Vec2 {
362        Vec2::splat(self)
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use std::f32::consts::TAU;
369
370    use super::*;
371
372    #[test]
373    fn local_vectors() {
374        let mut transform = Transform2d::from_rotation(TAU / 2.44);
375        assert_eq!(transform.local_y(), transform.rotation_matrix() * Vec2::Y);
376        assert_eq!(transform.local_x(), transform.rotation_matrix() * Vec2::X);
377        transform.rotation = TAU / -0.56;
378        assert_eq!(transform.local_y(), transform.rotation_matrix() * Vec2::Y);
379        assert_eq!(transform.local_x(), transform.rotation_matrix() * Vec2::X);
380    }
381}