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}