bevy_transform/components/transform.rs
1use super::GlobalTransform;
2use bevy_math::{Affine3A, Dir3, Isometry3d, Mat3, Mat4, Quat, Vec3};
3use core::ops::Mul;
4#[cfg(feature = "bevy-support")]
5use {
6 bevy_ecs::{component::Component, reflect::ReflectComponent},
7 bevy_reflect::prelude::*,
8};
9
10/// Describe the position of an entity. If the entity has a parent, the position is relative
11/// to its parent position.
12///
13/// * To place or move an entity, you should set its [`Transform`].
14/// * To get the global transform of an entity, you should get its [`GlobalTransform`].
15/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
16/// * ~You may use the [`TransformBundle`](crate::bundles::TransformBundle) to guarantee this.~
17/// [`TransformBundle`](crate::bundles::TransformBundle) is now deprecated.
18/// [`GlobalTransform`] is automatically inserted whenever [`Transform`] is inserted.
19///
20/// ## [`Transform`] and [`GlobalTransform`]
21///
22/// [`Transform`] is the position of an entity relative to its parent position, or the reference
23/// frame if it doesn't have a [`Parent`](bevy_hierarchy::Parent).
24///
25/// [`GlobalTransform`] is the position of an entity relative to the reference frame.
26///
27/// [`GlobalTransform`] is updated from [`Transform`] by systems in the system set
28/// [`TransformPropagate`](crate::TransformSystem::TransformPropagate).
29///
30/// This system runs during [`PostUpdate`](bevy_app::PostUpdate). If you
31/// update the [`Transform`] of an entity during this set or after, you will notice a 1 frame lag
32/// before the [`GlobalTransform`] is updated.
33///
34/// # Examples
35///
36/// - [`transform`][transform_example]
37///
38/// [transform_example]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/transform.rs
39#[derive(Debug, PartialEq, Clone, Copy)]
40#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
41#[cfg_attr(
42 feature = "bevy-support",
43 derive(Component, Reflect),
44 require(GlobalTransform),
45 reflect(Component, Default, PartialEq, Debug)
46)]
47#[cfg_attr(
48 all(feature = "bevy-support", feature = "serialize"),
49 reflect(Serialize, Deserialize)
50)]
51pub struct Transform {
52 /// Position of the entity. In 2d, the last value of the `Vec3` is used for z-ordering.
53 ///
54 /// See the [`translations`] example for usage.
55 ///
56 /// [`translations`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/translation.rs
57 pub translation: Vec3,
58 /// Rotation of the entity.
59 ///
60 /// See the [`3d_rotation`] example for usage.
61 ///
62 /// [`3d_rotation`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/3d_rotation.rs
63 pub rotation: Quat,
64 /// Scale of the entity.
65 ///
66 /// See the [`scale`] example for usage.
67 ///
68 /// [`scale`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/scale.rs
69 pub scale: Vec3,
70}
71
72impl Transform {
73 /// An identity [`Transform`] with no translation, rotation, and a scale of 1 on all axes.
74 pub const IDENTITY: Self = Transform {
75 translation: Vec3::ZERO,
76 rotation: Quat::IDENTITY,
77 scale: Vec3::ONE,
78 };
79
80 /// Creates a new [`Transform`] at the position `(x, y, z)`. In 2d, the `z` component
81 /// is used for z-ordering elements: higher `z`-value will be in front of lower
82 /// `z`-value.
83 #[inline]
84 pub const fn from_xyz(x: f32, y: f32, z: f32) -> Self {
85 Self::from_translation(Vec3::new(x, y, z))
86 }
87
88 /// Extracts the translation, rotation, and scale from `matrix`. It must be a 3d affine
89 /// transformation matrix.
90 #[inline]
91 pub fn from_matrix(world_from_local: Mat4) -> Self {
92 let (scale, rotation, translation) = world_from_local.to_scale_rotation_translation();
93
94 Transform {
95 translation,
96 rotation,
97 scale,
98 }
99 }
100
101 /// Creates a new [`Transform`], with `translation`. Rotation will be 0 and scale 1 on
102 /// all axes.
103 #[inline]
104 pub const fn from_translation(translation: Vec3) -> Self {
105 Transform {
106 translation,
107 ..Self::IDENTITY
108 }
109 }
110
111 /// Creates a new [`Transform`], with `rotation`. Translation will be 0 and scale 1 on
112 /// all axes.
113 #[inline]
114 pub const fn from_rotation(rotation: Quat) -> Self {
115 Transform {
116 rotation,
117 ..Self::IDENTITY
118 }
119 }
120
121 /// Creates a new [`Transform`], with `scale`. Translation will be 0 and rotation 0 on
122 /// all axes.
123 #[inline]
124 pub const fn from_scale(scale: Vec3) -> Self {
125 Transform {
126 scale,
127 ..Self::IDENTITY
128 }
129 }
130
131 /// Creates a new [`Transform`] that is equivalent to the given [isometry].
132 ///
133 /// [isometry]: Isometry3d
134 #[inline]
135 pub fn from_isometry(iso: Isometry3d) -> Self {
136 Transform {
137 translation: iso.translation.into(),
138 rotation: iso.rotation,
139 ..Self::IDENTITY
140 }
141 }
142
143 /// Returns this [`Transform`] with a new rotation so that [`Transform::forward`]
144 /// points towards the `target` position and [`Transform::up`] points towards `up`.
145 ///
146 /// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
147 /// * if `target` is the same as the transform translation, `Vec3::Z` is used instead
148 /// * if `up` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Y` is used instead
149 /// * if the resulting forward direction is parallel with `up`, an orthogonal vector is used as the "right" direction
150 #[inline]
151 #[must_use]
152 pub fn looking_at(mut self, target: Vec3, up: impl TryInto<Dir3>) -> Self {
153 self.look_at(target, up);
154 self
155 }
156
157 /// Returns this [`Transform`] with a new rotation so that [`Transform::forward`]
158 /// points in the given `direction` and [`Transform::up`] points towards `up`.
159 ///
160 /// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
161 /// * if `direction` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Z` is used instead
162 /// * if `up` fails converting to `Dir3`, `Dir3::Y` is used instead
163 /// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction
164 #[inline]
165 #[must_use]
166 pub fn looking_to(mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) -> Self {
167 self.look_to(direction, up);
168 self
169 }
170
171 /// Rotates this [`Transform`] so that the `main_axis` vector, reinterpreted in local coordinates, points
172 /// in the given `main_direction`, while `secondary_axis` points towards `secondary_direction`.
173 /// For example, if a spaceship model has its nose pointing in the X-direction in its own local coordinates
174 /// and its dorsal fin pointing in the Y-direction, then `align(Dir3::X, v, Dir3::Y, w)` will make the spaceship's
175 /// nose point in the direction of `v`, while the dorsal fin does its best to point in the direction `w`.
176 ///
177 ///
178 /// In some cases a rotation cannot be constructed. Another axis will be picked in those cases:
179 /// * if `main_axis` or `main_direction` fail converting to `Dir3` (e.g are zero), `Dir3::X` takes their place
180 /// * if `secondary_axis` or `secondary_direction` fail converting, `Dir3::Y` takes their place
181 /// * if `main_axis` is parallel with `secondary_axis` or `main_direction` is parallel with `secondary_direction`,
182 /// a rotation is constructed which takes `main_axis` to `main_direction` along a great circle, ignoring the secondary
183 /// counterparts
184 ///
185 /// See [`Transform::align`] for additional details.
186 #[inline]
187 #[must_use]
188 pub fn aligned_by(
189 mut self,
190 main_axis: impl TryInto<Dir3>,
191 main_direction: impl TryInto<Dir3>,
192 secondary_axis: impl TryInto<Dir3>,
193 secondary_direction: impl TryInto<Dir3>,
194 ) -> Self {
195 self.align(
196 main_axis,
197 main_direction,
198 secondary_axis,
199 secondary_direction,
200 );
201 self
202 }
203
204 /// Returns this [`Transform`] with a new translation.
205 #[inline]
206 #[must_use]
207 pub const fn with_translation(mut self, translation: Vec3) -> Self {
208 self.translation = translation;
209 self
210 }
211
212 /// Returns this [`Transform`] with a new rotation.
213 #[inline]
214 #[must_use]
215 pub const fn with_rotation(mut self, rotation: Quat) -> Self {
216 self.rotation = rotation;
217 self
218 }
219
220 /// Returns this [`Transform`] with a new scale.
221 #[inline]
222 #[must_use]
223 pub const fn with_scale(mut self, scale: Vec3) -> Self {
224 self.scale = scale;
225 self
226 }
227
228 /// Returns the 3d affine transformation matrix from this transforms translation,
229 /// rotation, and scale.
230 #[inline]
231 pub fn compute_matrix(&self) -> Mat4 {
232 Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
233 }
234
235 /// Returns the 3d affine transformation matrix from this transforms translation,
236 /// rotation, and scale.
237 #[inline]
238 pub fn compute_affine(&self) -> Affine3A {
239 Affine3A::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
240 }
241
242 /// Get the unit vector in the local `X` direction.
243 #[inline]
244 pub fn local_x(&self) -> Dir3 {
245 // Quat * unit vector is length 1
246 Dir3::new_unchecked(self.rotation * Vec3::X)
247 }
248
249 /// Equivalent to [`-local_x()`][Transform::local_x()]
250 #[inline]
251 pub fn left(&self) -> Dir3 {
252 -self.local_x()
253 }
254
255 /// Equivalent to [`local_x()`][Transform::local_x()]
256 #[inline]
257 pub fn right(&self) -> Dir3 {
258 self.local_x()
259 }
260
261 /// Get the unit vector in the local `Y` direction.
262 #[inline]
263 pub fn local_y(&self) -> Dir3 {
264 // Quat * unit vector is length 1
265 Dir3::new_unchecked(self.rotation * Vec3::Y)
266 }
267
268 /// Equivalent to [`local_y()`][Transform::local_y]
269 #[inline]
270 pub fn up(&self) -> Dir3 {
271 self.local_y()
272 }
273
274 /// Equivalent to [`-local_y()`][Transform::local_y]
275 #[inline]
276 pub fn down(&self) -> Dir3 {
277 -self.local_y()
278 }
279
280 /// Get the unit vector in the local `Z` direction.
281 #[inline]
282 pub fn local_z(&self) -> Dir3 {
283 // Quat * unit vector is length 1
284 Dir3::new_unchecked(self.rotation * Vec3::Z)
285 }
286
287 /// Equivalent to [`-local_z()`][Transform::local_z]
288 #[inline]
289 pub fn forward(&self) -> Dir3 {
290 -self.local_z()
291 }
292
293 /// Equivalent to [`local_z()`][Transform::local_z]
294 #[inline]
295 pub fn back(&self) -> Dir3 {
296 self.local_z()
297 }
298
299 /// Rotates this [`Transform`] by the given rotation.
300 ///
301 /// If this [`Transform`] has a parent, the `rotation` is relative to the rotation of the parent.
302 ///
303 /// # Examples
304 ///
305 /// - [`3d_rotation`]
306 ///
307 /// [`3d_rotation`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/3d_rotation.rs
308 #[inline]
309 pub fn rotate(&mut self, rotation: Quat) {
310 self.rotation = rotation * self.rotation;
311 }
312
313 /// Rotates this [`Transform`] around the given `axis` by `angle` (in radians).
314 ///
315 /// If this [`Transform`] has a parent, the `axis` is relative to the rotation of the parent.
316 #[inline]
317 pub fn rotate_axis(&mut self, axis: Dir3, angle: f32) {
318 self.rotate(Quat::from_axis_angle(axis.into(), angle));
319 }
320
321 /// Rotates this [`Transform`] around the `X` axis by `angle` (in radians).
322 ///
323 /// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
324 #[inline]
325 pub fn rotate_x(&mut self, angle: f32) {
326 self.rotate(Quat::from_rotation_x(angle));
327 }
328
329 /// Rotates this [`Transform`] around the `Y` axis by `angle` (in radians).
330 ///
331 /// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
332 #[inline]
333 pub fn rotate_y(&mut self, angle: f32) {
334 self.rotate(Quat::from_rotation_y(angle));
335 }
336
337 /// Rotates this [`Transform`] around the `Z` axis by `angle` (in radians).
338 ///
339 /// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
340 #[inline]
341 pub fn rotate_z(&mut self, angle: f32) {
342 self.rotate(Quat::from_rotation_z(angle));
343 }
344
345 /// Rotates this [`Transform`] by the given `rotation`.
346 ///
347 /// The `rotation` is relative to this [`Transform`]'s current rotation.
348 #[inline]
349 pub fn rotate_local(&mut self, rotation: Quat) {
350 self.rotation *= rotation;
351 }
352
353 /// Rotates this [`Transform`] around its local `axis` by `angle` (in radians).
354 #[inline]
355 pub fn rotate_local_axis(&mut self, axis: Dir3, angle: f32) {
356 self.rotate_local(Quat::from_axis_angle(axis.into(), angle));
357 }
358
359 /// Rotates this [`Transform`] around its local `X` axis by `angle` (in radians).
360 #[inline]
361 pub fn rotate_local_x(&mut self, angle: f32) {
362 self.rotate_local(Quat::from_rotation_x(angle));
363 }
364
365 /// Rotates this [`Transform`] around its local `Y` axis by `angle` (in radians).
366 #[inline]
367 pub fn rotate_local_y(&mut self, angle: f32) {
368 self.rotate_local(Quat::from_rotation_y(angle));
369 }
370
371 /// Rotates this [`Transform`] around its local `Z` axis by `angle` (in radians).
372 #[inline]
373 pub fn rotate_local_z(&mut self, angle: f32) {
374 self.rotate_local(Quat::from_rotation_z(angle));
375 }
376
377 /// Translates this [`Transform`] around a `point` in space.
378 ///
379 /// If this [`Transform`] has a parent, the `point` is relative to the [`Transform`] of the parent.
380 #[inline]
381 pub fn translate_around(&mut self, point: Vec3, rotation: Quat) {
382 self.translation = point + rotation * (self.translation - point);
383 }
384
385 /// Rotates this [`Transform`] around a `point` in space.
386 ///
387 /// If this [`Transform`] has a parent, the `point` is relative to the [`Transform`] of the parent.
388 #[inline]
389 pub fn rotate_around(&mut self, point: Vec3, rotation: Quat) {
390 self.translate_around(point, rotation);
391 self.rotate(rotation);
392 }
393
394 /// Rotates this [`Transform`] so that [`Transform::forward`] points towards the `target` position,
395 /// and [`Transform::up`] points towards `up`.
396 ///
397 /// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
398 /// * if `target` is the same as the transform translation, `Vec3::Z` is used instead
399 /// * if `up` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Y` is used instead
400 /// * if the resulting forward direction is parallel with `up`, an orthogonal vector is used as the "right" direction
401 #[inline]
402 pub fn look_at(&mut self, target: Vec3, up: impl TryInto<Dir3>) {
403 self.look_to(target - self.translation, up);
404 }
405
406 /// Rotates this [`Transform`] so that [`Transform::forward`] points in the given `direction`
407 /// and [`Transform::up`] points towards `up`.
408 ///
409 /// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
410 /// * if `direction` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::NEG_Z` is used instead
411 /// * if `up` fails converting to `Dir3`, `Dir3::Y` is used instead
412 /// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction
413 #[inline]
414 pub fn look_to(&mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) {
415 let back = -direction.try_into().unwrap_or(Dir3::NEG_Z);
416 let up = up.try_into().unwrap_or(Dir3::Y);
417 let right = up
418 .cross(back.into())
419 .try_normalize()
420 .unwrap_or_else(|| up.any_orthonormal_vector());
421 let up = back.cross(right);
422 self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, back.into()));
423 }
424
425 /// Rotates this [`Transform`] so that the `main_axis` vector, reinterpreted in local coordinates, points
426 /// in the given `main_direction`, while `secondary_axis` points towards `secondary_direction`.
427 ///
428 /// For example, if a spaceship model has its nose pointing in the X-direction in its own local coordinates
429 /// and its dorsal fin pointing in the Y-direction, then `align(Dir3::X, v, Dir3::Y, w)` will make the spaceship's
430 /// nose point in the direction of `v`, while the dorsal fin does its best to point in the direction `w`.
431 ///
432 /// More precisely, the [`Transform::rotation`] produced will be such that:
433 /// * applying it to `main_axis` results in `main_direction`
434 /// * applying it to `secondary_axis` produces a vector that lies in the half-plane generated by `main_direction` and
435 /// `secondary_direction` (with positive contribution by `secondary_direction`)
436 ///
437 /// [`Transform::look_to`] is recovered, for instance, when `main_axis` is `Dir3::NEG_Z` (the [`Transform::forward`]
438 /// direction in the default orientation) and `secondary_axis` is `Dir3::Y` (the [`Transform::up`] direction in the default
439 /// orientation). (Failure cases may differ somewhat.)
440 ///
441 /// In some cases a rotation cannot be constructed. Another axis will be picked in those cases:
442 /// * if `main_axis` or `main_direction` fail converting to `Dir3` (e.g are zero), `Dir3::X` takes their place
443 /// * if `secondary_axis` or `secondary_direction` fail converting, `Dir3::Y` takes their place
444 /// * if `main_axis` is parallel with `secondary_axis` or `main_direction` is parallel with `secondary_direction`,
445 /// a rotation is constructed which takes `main_axis` to `main_direction` along a great circle, ignoring the secondary
446 /// counterparts
447 ///
448 /// Example
449 /// ```
450 /// # use bevy_math::{Dir3, Vec3, Quat};
451 /// # use bevy_transform::components::Transform;
452 /// # let mut t1 = Transform::IDENTITY;
453 /// # let mut t2 = Transform::IDENTITY;
454 /// t1.align(Dir3::X, Dir3::Y, Vec3::new(1., 1., 0.), Dir3::Z);
455 /// let main_axis_image = t1.rotation * Dir3::X;
456 /// let secondary_axis_image = t1.rotation * Vec3::new(1., 1., 0.);
457 /// assert!(main_axis_image.abs_diff_eq(Vec3::Y, 1e-5));
458 /// assert!(secondary_axis_image.abs_diff_eq(Vec3::new(0., 1., 1.), 1e-5));
459 ///
460 /// t1.align(Vec3::ZERO, Dir3::Z, Vec3::ZERO, Dir3::X);
461 /// t2.align(Dir3::X, Dir3::Z, Dir3::Y, Dir3::X);
462 /// assert_eq!(t1.rotation, t2.rotation);
463 ///
464 /// t1.align(Dir3::X, Dir3::Z, Dir3::X, Dir3::Y);
465 /// assert_eq!(t1.rotation, Quat::from_rotation_arc(Vec3::X, Vec3::Z));
466 /// ```
467 #[inline]
468 pub fn align(
469 &mut self,
470 main_axis: impl TryInto<Dir3>,
471 main_direction: impl TryInto<Dir3>,
472 secondary_axis: impl TryInto<Dir3>,
473 secondary_direction: impl TryInto<Dir3>,
474 ) {
475 let main_axis = main_axis.try_into().unwrap_or(Dir3::X);
476 let main_direction = main_direction.try_into().unwrap_or(Dir3::X);
477 let secondary_axis = secondary_axis.try_into().unwrap_or(Dir3::Y);
478 let secondary_direction = secondary_direction.try_into().unwrap_or(Dir3::Y);
479
480 // The solution quaternion will be constructed in two steps.
481 // First, we start with a rotation that takes `main_axis` to `main_direction`.
482 let first_rotation = Quat::from_rotation_arc(main_axis.into(), main_direction.into());
483
484 // Let's follow by rotating about the `main_direction` axis so that the image of `secondary_axis`
485 // is taken to something that lies in the plane of `main_direction` and `secondary_direction`. Since
486 // `main_direction` is fixed by this rotation, the first criterion is still satisfied.
487 let secondary_image = first_rotation * secondary_axis;
488 let secondary_image_ortho = secondary_image
489 .reject_from_normalized(main_direction.into())
490 .try_normalize();
491 let secondary_direction_ortho = secondary_direction
492 .reject_from_normalized(main_direction.into())
493 .try_normalize();
494
495 // If one of the two weak vectors was parallel to `main_direction`, then we just do the first part
496 self.rotation = match (secondary_image_ortho, secondary_direction_ortho) {
497 (Some(secondary_img_ortho), Some(secondary_dir_ortho)) => {
498 let second_rotation =
499 Quat::from_rotation_arc(secondary_img_ortho, secondary_dir_ortho);
500 second_rotation * first_rotation
501 }
502 _ => first_rotation,
503 };
504 }
505
506 /// Multiplies `self` with `transform` component by component, returning the
507 /// resulting [`Transform`]
508 #[inline]
509 #[must_use]
510 pub fn mul_transform(&self, transform: Transform) -> Self {
511 let translation = self.transform_point(transform.translation);
512 let rotation = self.rotation * transform.rotation;
513 let scale = self.scale * transform.scale;
514 Transform {
515 translation,
516 rotation,
517 scale,
518 }
519 }
520
521 /// Transforms the given `point`, applying scale, rotation and translation.
522 ///
523 /// If this [`Transform`] has an ancestor entity with a [`Transform`] component,
524 /// [`Transform::transform_point`] will transform a point in local space into its
525 /// parent transform's space.
526 ///
527 /// If this [`Transform`] does not have a parent, [`Transform::transform_point`] will
528 /// transform a point in local space into worldspace coordinates.
529 ///
530 /// If you always want to transform a point in local space to worldspace, or if you need
531 /// the inverse transformations, see [`GlobalTransform::transform_point()`].
532 #[inline]
533 pub fn transform_point(&self, mut point: Vec3) -> Vec3 {
534 point = self.scale * point;
535 point = self.rotation * point;
536 point += self.translation;
537 point
538 }
539
540 /// Returns `true` if, and only if, translation, rotation and scale all are
541 /// finite. If any of them contains a `NaN`, positive or negative infinity,
542 /// this will return `false`.
543 #[inline]
544 #[must_use]
545 pub fn is_finite(&self) -> bool {
546 self.translation.is_finite() && self.rotation.is_finite() && self.scale.is_finite()
547 }
548
549 /// Get the [isometry] defined by this transform's rotation and translation, ignoring scale.
550 ///
551 /// [isometry]: Isometry3d
552 #[inline]
553 pub fn to_isometry(&self) -> Isometry3d {
554 Isometry3d::new(self.translation, self.rotation)
555 }
556}
557
558impl Default for Transform {
559 fn default() -> Self {
560 Self::IDENTITY
561 }
562}
563
564/// The transform is expected to be non-degenerate and without shearing, or the output
565/// will be invalid.
566impl From<GlobalTransform> for Transform {
567 fn from(transform: GlobalTransform) -> Self {
568 transform.compute_transform()
569 }
570}
571
572impl Mul<Transform> for Transform {
573 type Output = Transform;
574
575 fn mul(self, transform: Transform) -> Self::Output {
576 self.mul_transform(transform)
577 }
578}
579
580impl Mul<GlobalTransform> for Transform {
581 type Output = GlobalTransform;
582
583 #[inline]
584 fn mul(self, global_transform: GlobalTransform) -> Self::Output {
585 GlobalTransform::from(self) * global_transform
586 }
587}
588
589impl Mul<Vec3> for Transform {
590 type Output = Vec3;
591
592 fn mul(self, value: Vec3) -> Self::Output {
593 self.transform_point(value)
594 }
595}