Path: blob/main/crates/bevy_transform/src/components/transform.rs
6598 views
use super::GlobalTransform;1use bevy_math::{Affine3A, Dir3, Isometry3d, Mat3, Mat4, Quat, Vec3};2use core::ops::Mul;34#[cfg(feature = "bevy-support")]5use bevy_ecs::component::Component;67#[cfg(feature = "bevy_reflect")]8use {bevy_ecs::reflect::ReflectComponent, bevy_reflect::prelude::*};910/// Checks that a vector with the given squared length is normalized.11///12/// Warns for small error with a length threshold of approximately `1e-4`,13/// and panics for large error with a length threshold of approximately `1e-2`.14#[cfg(debug_assertions)]15fn assert_is_normalized(message: &str, length_squared: f32) {16use bevy_math::ops;17#[cfg(feature = "std")]18use std::eprintln;1920let length_error_squared = ops::abs(length_squared - 1.0);2122// Panic for large error and warn for slight error.23if length_error_squared > 2e-2 || length_error_squared.is_nan() {24// Length error is approximately 1e-2 or more.25panic!("Error: {message}",);26} else if length_error_squared > 2e-4 {27// Length error is approximately 1e-4 or more.28#[cfg(feature = "std")]29#[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]30{31eprintln!("Warning: {message}",);32}33}34}3536/// Describe the position of an entity. If the entity has a parent, the position is relative37/// to its parent position.38///39/// * To place or move an entity, you should set its [`Transform`].40/// * To get the global transform of an entity, you should get its [`GlobalTransform`].41/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].42/// [`GlobalTransform`] is automatically inserted whenever [`Transform`] is inserted.43///44/// ## [`Transform`] and [`GlobalTransform`]45///46/// [`Transform`] is the position of an entity relative to its parent position, or the reference47/// frame if it doesn't have a [`ChildOf`](bevy_ecs::hierarchy::ChildOf) component.48///49/// [`GlobalTransform`] is the position of an entity relative to the reference frame.50///51/// [`GlobalTransform`] is updated from [`Transform`] in the [`TransformSystems::Propagate`]52/// system set.53///54/// This system runs during [`PostUpdate`](bevy_app::PostUpdate). If you55/// update the [`Transform`] of an entity during this set or after, you will notice a 1 frame lag56/// before the [`GlobalTransform`] is updated.57///58/// [`TransformSystems::Propagate`]: crate::TransformSystems::Propagate59///60/// # Examples61///62/// - [`transform`][transform_example]63///64/// [transform_example]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/transform.rs65#[derive(Debug, PartialEq, Clone, Copy)]66#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]67#[cfg_attr(68feature = "bevy-support",69derive(Component),70require(GlobalTransform, TransformTreeChanged)71)]72#[cfg_attr(73feature = "bevy_reflect",74derive(Reflect),75reflect(Component, Default, PartialEq, Debug, Clone)76)]77#[cfg_attr(78all(feature = "bevy_reflect", feature = "serialize"),79reflect(Serialize, Deserialize)80)]81pub struct Transform {82/// Position of the entity. In 2d, the last value of the `Vec3` is used for z-ordering.83///84/// See the [`translations`] example for usage.85///86/// [`translations`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/translation.rs87pub translation: Vec3,88/// Rotation of the entity.89///90/// See the [`3d_rotation`] example for usage.91///92/// [`3d_rotation`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/3d_rotation.rs93pub rotation: Quat,94/// Scale of the entity.95///96/// See the [`scale`] example for usage.97///98/// [`scale`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/scale.rs99pub scale: Vec3,100}101102impl Transform {103/// An identity [`Transform`] with no translation, rotation, and a scale of 1 on all axes.104pub const IDENTITY: Self = Transform {105translation: Vec3::ZERO,106rotation: Quat::IDENTITY,107scale: Vec3::ONE,108};109110/// Creates a new [`Transform`] at the position `(x, y, z)`. In 2d, the `z` component111/// is used for z-ordering elements: higher `z`-value will be in front of lower112/// `z`-value.113#[inline]114pub const fn from_xyz(x: f32, y: f32, z: f32) -> Self {115Self::from_translation(Vec3::new(x, y, z))116}117118/// Extracts the translation, rotation, and scale from `matrix`. It must be a 3d affine119/// transformation matrix.120#[inline]121pub fn from_matrix(world_from_local: Mat4) -> Self {122let (scale, rotation, translation) = world_from_local.to_scale_rotation_translation();123124Transform {125translation,126rotation,127scale,128}129}130131/// Creates a new [`Transform`], with `translation`. Rotation will be 0 and scale 1 on132/// all axes.133#[inline]134pub const fn from_translation(translation: Vec3) -> Self {135Transform {136translation,137..Self::IDENTITY138}139}140141/// Creates a new [`Transform`], with `rotation`. Translation will be 0 and scale 1 on142/// all axes.143#[inline]144pub const fn from_rotation(rotation: Quat) -> Self {145Transform {146rotation,147..Self::IDENTITY148}149}150151/// Creates a new [`Transform`], with `scale`. Translation will be 0 and rotation 0 on152/// all axes.153#[inline]154pub const fn from_scale(scale: Vec3) -> Self {155Transform {156scale,157..Self::IDENTITY158}159}160161/// Creates a new [`Transform`] that is equivalent to the given [isometry].162///163/// [isometry]: Isometry3d164#[inline]165pub fn from_isometry(iso: Isometry3d) -> Self {166Transform {167translation: iso.translation.into(),168rotation: iso.rotation,169..Self::IDENTITY170}171}172173/// Returns this [`Transform`] with a new rotation so that [`Transform::forward`]174/// points towards the `target` position and [`Transform::up`] points towards `up`.175///176/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:177/// * if `target` is the same as the transform translation, `Vec3::Z` is used instead178/// * if `up` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Y` is used instead179/// * if the resulting forward direction is parallel with `up`, an orthogonal vector is used as the "right" direction180#[inline]181#[must_use]182pub fn looking_at(mut self, target: Vec3, up: impl TryInto<Dir3>) -> Self {183self.look_at(target, up);184self185}186187/// Returns this [`Transform`] with a new rotation so that [`Transform::forward`]188/// points in the given `direction` and [`Transform::up`] points towards `up`.189///190/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:191/// * if `direction` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Z` is used instead192/// * if `up` fails converting to `Dir3`, `Dir3::Y` is used instead193/// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction194#[inline]195#[must_use]196pub fn looking_to(mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) -> Self {197self.look_to(direction, up);198self199}200201/// Rotates this [`Transform`] so that the `main_axis` vector, reinterpreted in local coordinates, points202/// in the given `main_direction`, while `secondary_axis` points towards `secondary_direction`.203/// For example, if a spaceship model has its nose pointing in the X-direction in its own local coordinates204/// and its dorsal fin pointing in the Y-direction, then `align(Dir3::X, v, Dir3::Y, w)` will make the spaceship's205/// nose point in the direction of `v`, while the dorsal fin does its best to point in the direction `w`.206///207///208/// In some cases a rotation cannot be constructed. Another axis will be picked in those cases:209/// * if `main_axis` or `main_direction` fail converting to `Dir3` (e.g are zero), `Dir3::X` takes their place210/// * if `secondary_axis` or `secondary_direction` fail converting, `Dir3::Y` takes their place211/// * if `main_axis` is parallel with `secondary_axis` or `main_direction` is parallel with `secondary_direction`,212/// a rotation is constructed which takes `main_axis` to `main_direction` along a great circle, ignoring the secondary213/// counterparts214///215/// See [`Transform::align`] for additional details.216#[inline]217#[must_use]218pub fn aligned_by(219mut self,220main_axis: impl TryInto<Dir3>,221main_direction: impl TryInto<Dir3>,222secondary_axis: impl TryInto<Dir3>,223secondary_direction: impl TryInto<Dir3>,224) -> Self {225self.align(226main_axis,227main_direction,228secondary_axis,229secondary_direction,230);231self232}233234/// Returns this [`Transform`] with a new translation.235#[inline]236#[must_use]237pub const fn with_translation(mut self, translation: Vec3) -> Self {238self.translation = translation;239self240}241242/// Returns this [`Transform`] with a new rotation.243#[inline]244#[must_use]245pub const fn with_rotation(mut self, rotation: Quat) -> Self {246self.rotation = rotation;247self248}249250/// Returns this [`Transform`] with a new scale.251#[inline]252#[must_use]253pub const fn with_scale(mut self, scale: Vec3) -> Self {254self.scale = scale;255self256}257258/// Computes the 3d affine transformation matrix from this transform's translation,259/// rotation, and scale.260#[inline]261pub fn to_matrix(&self) -> Mat4 {262Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)263}264265/// Returns the 3d affine transformation matrix from this transforms translation,266/// rotation, and scale.267#[inline]268pub fn compute_affine(&self) -> Affine3A {269Affine3A::from_scale_rotation_translation(self.scale, self.rotation, self.translation)270}271272/// Get the unit vector in the local `X` direction.273#[inline]274pub fn local_x(&self) -> Dir3 {275// Quat * unit vector is length 1276Dir3::new_unchecked(self.rotation * Vec3::X)277}278279/// Equivalent to [`-local_x()`][Transform::local_x()]280#[inline]281pub fn left(&self) -> Dir3 {282-self.local_x()283}284285/// Equivalent to [`local_x()`][Transform::local_x()]286#[inline]287pub fn right(&self) -> Dir3 {288self.local_x()289}290291/// Get the unit vector in the local `Y` direction.292#[inline]293pub fn local_y(&self) -> Dir3 {294// Quat * unit vector is length 1295Dir3::new_unchecked(self.rotation * Vec3::Y)296}297298/// Equivalent to [`local_y()`][Transform::local_y]299#[inline]300pub fn up(&self) -> Dir3 {301self.local_y()302}303304/// Equivalent to [`-local_y()`][Transform::local_y]305#[inline]306pub fn down(&self) -> Dir3 {307-self.local_y()308}309310/// Get the unit vector in the local `Z` direction.311#[inline]312pub fn local_z(&self) -> Dir3 {313// Quat * unit vector is length 1314Dir3::new_unchecked(self.rotation * Vec3::Z)315}316317/// Equivalent to [`-local_z()`][Transform::local_z]318#[inline]319pub fn forward(&self) -> Dir3 {320-self.local_z()321}322323/// Equivalent to [`local_z()`][Transform::local_z]324#[inline]325pub fn back(&self) -> Dir3 {326self.local_z()327}328329/// Rotates this [`Transform`] by the given rotation.330///331/// If this [`Transform`] has a parent, the `rotation` is relative to the rotation of the parent.332///333/// # Examples334///335/// - [`3d_rotation`]336///337/// [`3d_rotation`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/3d_rotation.rs338#[inline]339pub fn rotate(&mut self, rotation: Quat) {340self.rotation = rotation * self.rotation;341}342343/// Rotates this [`Transform`] around the given `axis` by `angle` (in radians).344///345/// If this [`Transform`] has a parent, the `axis` is relative to the rotation of the parent.346///347/// # Warning348///349/// If you pass in an `axis` based on the current rotation (e.g. obtained via [`Transform::local_x`]),350/// floating point errors can accumulate exponentially when applying rotations repeatedly this way. This will351/// result in a denormalized rotation. In this case, it is recommended to normalize the [`Transform::rotation`] after352/// each call to this method.353#[inline]354pub fn rotate_axis(&mut self, axis: Dir3, angle: f32) {355#[cfg(debug_assertions)]356assert_is_normalized(357"The axis given to `Transform::rotate_axis` is not normalized. This may be a result of obtaining \358the axis from the transform. See the documentation of `Transform::rotate_axis` for more details.",359axis.length_squared(),360);361self.rotate(Quat::from_axis_angle(axis.into(), angle));362}363364/// Rotates this [`Transform`] around the `X` axis by `angle` (in radians).365///366/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.367#[inline]368pub fn rotate_x(&mut self, angle: f32) {369self.rotate(Quat::from_rotation_x(angle));370}371372/// Rotates this [`Transform`] around the `Y` axis by `angle` (in radians).373///374/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.375#[inline]376pub fn rotate_y(&mut self, angle: f32) {377self.rotate(Quat::from_rotation_y(angle));378}379380/// Rotates this [`Transform`] around the `Z` axis by `angle` (in radians).381///382/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.383#[inline]384pub fn rotate_z(&mut self, angle: f32) {385self.rotate(Quat::from_rotation_z(angle));386}387388/// Rotates this [`Transform`] by the given `rotation`.389///390/// The `rotation` is relative to this [`Transform`]'s current rotation.391#[inline]392pub fn rotate_local(&mut self, rotation: Quat) {393self.rotation *= rotation;394}395396/// Rotates this [`Transform`] around its local `axis` by `angle` (in radians).397///398/// # Warning399///400/// If you pass in an `axis` based on the current rotation (e.g. obtained via [`Transform::local_x`]),401/// floating point errors can accumulate exponentially when applying rotations repeatedly this way. This will402/// result in a denormalized rotation. In this case, it is recommended to normalize the [`Transform::rotation`] after403/// each call to this method.404#[inline]405pub fn rotate_local_axis(&mut self, axis: Dir3, angle: f32) {406#[cfg(debug_assertions)]407assert_is_normalized(408"The axis given to `Transform::rotate_axis_local` is not normalized. This may be a result of obtaining \409the axis from the transform. See the documentation of `Transform::rotate_axis_local` for more details.",410axis.length_squared(),411);412self.rotate_local(Quat::from_axis_angle(axis.into(), angle));413}414415/// Rotates this [`Transform`] around its local `X` axis by `angle` (in radians).416#[inline]417pub fn rotate_local_x(&mut self, angle: f32) {418self.rotate_local(Quat::from_rotation_x(angle));419}420421/// Rotates this [`Transform`] around its local `Y` axis by `angle` (in radians).422#[inline]423pub fn rotate_local_y(&mut self, angle: f32) {424self.rotate_local(Quat::from_rotation_y(angle));425}426427/// Rotates this [`Transform`] around its local `Z` axis by `angle` (in radians).428#[inline]429pub fn rotate_local_z(&mut self, angle: f32) {430self.rotate_local(Quat::from_rotation_z(angle));431}432433/// Translates this [`Transform`] around a `point` in space.434///435/// If this [`Transform`] has a parent, the `point` is relative to the [`Transform`] of the parent.436#[inline]437pub fn translate_around(&mut self, point: Vec3, rotation: Quat) {438self.translation = point + rotation * (self.translation - point);439}440441/// Rotates this [`Transform`] around a `point` in space.442///443/// If this [`Transform`] has a parent, the `point` is relative to the [`Transform`] of the parent.444#[inline]445pub fn rotate_around(&mut self, point: Vec3, rotation: Quat) {446self.translate_around(point, rotation);447self.rotate(rotation);448}449450/// Rotates this [`Transform`] so that [`Transform::forward`] points towards the `target` position,451/// and [`Transform::up`] points towards `up`.452///453/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:454/// * if `target` is the same as the transform translation, `Vec3::Z` is used instead455/// * if `up` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Y` is used instead456/// * if the resulting forward direction is parallel with `up`, an orthogonal vector is used as the "right" direction457#[inline]458pub fn look_at(&mut self, target: Vec3, up: impl TryInto<Dir3>) {459self.look_to(target - self.translation, up);460}461462/// Rotates this [`Transform`] so that [`Transform::forward`] points in the given `direction`463/// and [`Transform::up`] points towards `up`.464///465/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:466/// * if `direction` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::NEG_Z` is used instead467/// * if `up` fails converting to `Dir3`, `Dir3::Y` is used instead468/// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction469#[inline]470pub fn look_to(&mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) {471let back = -direction.try_into().unwrap_or(Dir3::NEG_Z);472let up = up.try_into().unwrap_or(Dir3::Y);473let right = up474.cross(back.into())475.try_normalize()476.unwrap_or_else(|| up.any_orthonormal_vector());477let up = back.cross(right);478self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, back.into()));479}480481/// Rotates this [`Transform`] so that the `main_axis` vector, reinterpreted in local coordinates, points482/// in the given `main_direction`, while `secondary_axis` points towards `secondary_direction`.483///484/// For example, if a spaceship model has its nose pointing in the X-direction in its own local coordinates485/// and its dorsal fin pointing in the Y-direction, then `align(Dir3::X, v, Dir3::Y, w)` will make the spaceship's486/// nose point in the direction of `v`, while the dorsal fin does its best to point in the direction `w`.487///488/// More precisely, the [`Transform::rotation`] produced will be such that:489/// * applying it to `main_axis` results in `main_direction`490/// * applying it to `secondary_axis` produces a vector that lies in the half-plane generated by `main_direction` and491/// `secondary_direction` (with positive contribution by `secondary_direction`)492///493/// [`Transform::look_to`] is recovered, for instance, when `main_axis` is `Dir3::NEG_Z` (the [`Transform::forward`]494/// direction in the default orientation) and `secondary_axis` is `Dir3::Y` (the [`Transform::up`] direction in the default495/// orientation). (Failure cases may differ somewhat.)496///497/// In some cases a rotation cannot be constructed. Another axis will be picked in those cases:498/// * if `main_axis` or `main_direction` fail converting to `Dir3` (e.g are zero), `Dir3::X` takes their place499/// * if `secondary_axis` or `secondary_direction` fail converting, `Dir3::Y` takes their place500/// * if `main_axis` is parallel with `secondary_axis` or `main_direction` is parallel with `secondary_direction`,501/// a rotation is constructed which takes `main_axis` to `main_direction` along a great circle, ignoring the secondary502/// counterparts503///504/// Example505/// ```506/// # use bevy_math::{Dir3, Vec3, Quat};507/// # use bevy_transform::components::Transform;508/// # let mut t1 = Transform::IDENTITY;509/// # let mut t2 = Transform::IDENTITY;510/// t1.align(Dir3::X, Dir3::Y, Vec3::new(1., 1., 0.), Dir3::Z);511/// let main_axis_image = t1.rotation * Dir3::X;512/// let secondary_axis_image = t1.rotation * Vec3::new(1., 1., 0.);513/// assert!(main_axis_image.abs_diff_eq(Vec3::Y, 1e-5));514/// assert!(secondary_axis_image.abs_diff_eq(Vec3::new(0., 1., 1.), 1e-5));515///516/// t1.align(Vec3::ZERO, Dir3::Z, Vec3::ZERO, Dir3::X);517/// t2.align(Dir3::X, Dir3::Z, Dir3::Y, Dir3::X);518/// assert_eq!(t1.rotation, t2.rotation);519///520/// t1.align(Dir3::X, Dir3::Z, Dir3::X, Dir3::Y);521/// assert_eq!(t1.rotation, Quat::from_rotation_arc(Vec3::X, Vec3::Z));522/// ```523#[inline]524pub fn align(525&mut self,526main_axis: impl TryInto<Dir3>,527main_direction: impl TryInto<Dir3>,528secondary_axis: impl TryInto<Dir3>,529secondary_direction: impl TryInto<Dir3>,530) {531let main_axis = main_axis.try_into().unwrap_or(Dir3::X);532let main_direction = main_direction.try_into().unwrap_or(Dir3::X);533let secondary_axis = secondary_axis.try_into().unwrap_or(Dir3::Y);534let secondary_direction = secondary_direction.try_into().unwrap_or(Dir3::Y);535536// The solution quaternion will be constructed in two steps.537// First, we start with a rotation that takes `main_axis` to `main_direction`.538let first_rotation = Quat::from_rotation_arc(main_axis.into(), main_direction.into());539540// Let's follow by rotating about the `main_direction` axis so that the image of `secondary_axis`541// is taken to something that lies in the plane of `main_direction` and `secondary_direction`. Since542// `main_direction` is fixed by this rotation, the first criterion is still satisfied.543let secondary_image = first_rotation * secondary_axis;544let secondary_image_ortho = secondary_image545.reject_from_normalized(main_direction.into())546.try_normalize();547let secondary_direction_ortho = secondary_direction548.reject_from_normalized(main_direction.into())549.try_normalize();550551// If one of the two weak vectors was parallel to `main_direction`, then we just do the first part552self.rotation = match (secondary_image_ortho, secondary_direction_ortho) {553(Some(secondary_img_ortho), Some(secondary_dir_ortho)) => {554let second_rotation =555Quat::from_rotation_arc(secondary_img_ortho, secondary_dir_ortho);556second_rotation * first_rotation557}558_ => first_rotation,559};560}561562/// Multiplies `self` with `transform` component by component, returning the563/// resulting [`Transform`]564#[inline]565#[must_use]566pub fn mul_transform(&self, transform: Transform) -> Self {567let translation = self.transform_point(transform.translation);568let rotation = self.rotation * transform.rotation;569let scale = self.scale * transform.scale;570Transform {571translation,572rotation,573scale,574}575}576577/// Transforms the given `point`, applying scale, rotation and translation.578///579/// If this [`Transform`] has an ancestor entity with a [`Transform`] component,580/// [`Transform::transform_point`] will transform a point in local space into its581/// parent transform's space.582///583/// If this [`Transform`] does not have a parent, [`Transform::transform_point`] will584/// transform a point in local space into worldspace coordinates.585///586/// If you always want to transform a point in local space to worldspace, or if you need587/// the inverse transformations, see [`GlobalTransform::transform_point()`].588#[inline]589pub fn transform_point(&self, mut point: Vec3) -> Vec3 {590point = self.scale * point;591point = self.rotation * point;592point += self.translation;593point594}595596/// Returns `true` if, and only if, translation, rotation and scale all are597/// finite. If any of them contains a `NaN`, positive or negative infinity,598/// this will return `false`.599#[inline]600#[must_use]601pub fn is_finite(&self) -> bool {602self.translation.is_finite() && self.rotation.is_finite() && self.scale.is_finite()603}604605/// Get the [isometry] defined by this transform's rotation and translation, ignoring scale.606///607/// [isometry]: Isometry3d608#[inline]609pub fn to_isometry(&self) -> Isometry3d {610Isometry3d::new(self.translation, self.rotation)611}612}613614impl Default for Transform {615fn default() -> Self {616Self::IDENTITY617}618}619620/// The transform is expected to be non-degenerate and without shearing, or the output621/// will be invalid.622impl From<GlobalTransform> for Transform {623fn from(transform: GlobalTransform) -> Self {624transform.compute_transform()625}626}627628impl Mul<Transform> for Transform {629type Output = Transform;630631fn mul(self, transform: Transform) -> Self::Output {632self.mul_transform(transform)633}634}635636impl Mul<GlobalTransform> for Transform {637type Output = GlobalTransform;638639#[inline]640fn mul(self, global_transform: GlobalTransform) -> Self::Output {641GlobalTransform::from(self) * global_transform642}643}644645impl Mul<Vec3> for Transform {646type Output = Vec3;647648fn mul(self, value: Vec3) -> Self::Output {649self.transform_point(value)650}651}652653/// An optimization for transform propagation. This ZST marker component uses change detection to654/// mark all entities of the hierarchy as "dirty" if any of their descendants have a changed655/// `Transform`. If this component is *not* marked `is_changed()`, propagation will halt.656#[derive(Clone, Copy, Default, PartialEq, Debug)]657#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]658#[cfg_attr(feature = "bevy-support", derive(Component))]659#[cfg_attr(660feature = "bevy_reflect",661derive(Reflect),662reflect(Component, Default, PartialEq, Debug)663)]664#[cfg_attr(665all(feature = "bevy_reflect", feature = "serialize"),666reflect(Serialize, Deserialize)667)]668pub struct TransformTreeChanged;669670671