diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index 8fae0724f679a..bbf059cf25dfd 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -106,6 +106,50 @@ impl GlobalTransform { } } + /// Returns the [`Transform`] `self` would have if it was a child of an entity + /// with the `parent` [`GlobalTransform`]. + /// + /// This is useful if you want to "reparent" an `Entity`. Say you have an entity + /// `e1` that you want to turn into a child of `e2`, but you want `e1` to keep the + /// same global transform, even after re-partenting. You would use: + /// + /// ```rust + /// # use bevy_transform::prelude::{GlobalTransform, Transform}; + /// # use bevy_ecs::prelude::{Entity, Query, Component, Commands}; + /// # use bevy_hierarchy::{prelude::Parent, BuildChildren}; + /// #[derive(Component)] + /// struct ToReparent { + /// new_parent: Entity, + /// } + /// fn reparent_system( + /// mut commands: Commands, + /// mut targets: Query<(&mut Transform, Entity, &GlobalTransform, &ToReparent)>, + /// transforms: Query<&GlobalTransform>, + /// ) { + /// for (mut transform, entity, initial, to_reparent) in targets.iter_mut() { + /// if let Ok(parent_transform) = transforms.get(to_reparent.new_parent) { + /// *transform = initial.reparented_to(parent_transform); + /// commands.entity(entity) + /// .remove::() + /// .set_parent(to_reparent.new_parent); + /// } + /// } + /// } + /// ``` + /// + /// The transform is expected to be non-degenerate and without shearing, or the output + /// will be invalid. + #[inline] + pub fn reparented_to(&self, parent: &GlobalTransform) -> Transform { + let relative_affine = parent.affine().inverse() * self.affine(); + let (scale, rotation, translation) = relative_affine.to_scale_rotation_translation(); + Transform { + translation, + rotation, + scale, + } + } + /// Extracts `scale`, `rotation` and `translation` from `self`. /// /// The transform is expected to be non-degenerate and without shearing, or the output @@ -209,3 +253,60 @@ impl Mul for GlobalTransform { self.transform_point(value) } } + +#[cfg(test)] +mod test { + use super::*; + + use bevy_math::EulerRot::XYZ; + + fn transform_equal(left: GlobalTransform, right: Transform) -> bool { + left.0.abs_diff_eq(right.compute_affine(), 0.01) + } + + #[test] + fn reparented_to_transform_identity() { + fn reparent_to_same(t1: GlobalTransform, t2: GlobalTransform) -> Transform { + t2.mul_transform(t1.into()).reparented_to(&t2) + } + let t1 = GlobalTransform::from(Transform { + translation: Vec3::new(1034.0, 34.0, -1324.34), + rotation: Quat::from_euler(XYZ, 1.0, 0.9, 2.1), + scale: Vec3::new(1.0, 1.0, 1.0), + }); + let t2 = GlobalTransform::from(Transform { + translation: Vec3::new(0.0, -54.493, 324.34), + rotation: Quat::from_euler(XYZ, 1.9, 0.3, 3.0), + scale: Vec3::new(1.345, 1.345, 1.345), + }); + let retransformed = reparent_to_same(t1, t2); + assert!( + transform_equal(t1, retransformed), + "t1:{:#?} retransformed:{:#?}", + t1.compute_transform(), + retransformed, + ); + } + #[test] + fn reparented_usecase() { + let t1 = GlobalTransform::from(Transform { + translation: Vec3::new(1034.0, 34.0, -1324.34), + rotation: Quat::from_euler(XYZ, 0.8, 1.9, 2.1), + scale: Vec3::new(10.9, 10.9, 10.9), + }); + let t2 = GlobalTransform::from(Transform { + translation: Vec3::new(28.0, -54.493, 324.34), + rotation: Quat::from_euler(XYZ, 0.0, 3.1, 0.1), + scale: Vec3::new(0.9, 0.9, 0.9), + }); + // goal: find `X` such as `t2 * X = t1` + let reparented = t1.reparented_to(&t2); + let t1_prime = t2 * reparented; + assert!( + transform_equal(t1, t1_prime.into()), + "t1:{:#?} t1_prime:{:#?}", + t1.compute_transform(), + t1_prime.compute_transform(), + ); + } +}