From cce1f3c71a35bcdd880f07afa36ec475f02eb249 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Wed, 1 Jun 2022 15:32:46 +0200 Subject: [PATCH 1/2] Add `reparented_to` method to `GlobalTransform` Finding the proper transform to make an entity a child of another without its global transform changing is tricky and error prone. It requires some knowledge of how bevy computes and propagates `GlobalTransform` through the hierarchy. This introduces a way to find such a transform. The method is only implemented on `GlobalTransform` since we expect it to be the most common use-case. --- .../src/components/global_transform.rs | 84 ++++++++++++++++++- .../src/components/transform.rs | 6 +- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index be1d75cfcb46b..7601599c48e3a 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -206,7 +206,11 @@ impl GlobalTransform { } /// Multiplies `self` with `transform` component by component, returning the - /// resulting [`GlobalTransform`] + /// resulting [`GlobalTransform`]. + /// + /// Note that `self.mul_transform(transform)` is identical to `self * transform`. + /// + /// To find `X` such as `transform * X = self`, see [`Self::reparented_to`]. #[inline] #[must_use] pub fn mul_transform(&self, transform: Transform) -> Self { @@ -220,6 +224,50 @@ impl GlobalTransform { } } + /// Returns `X` such as `transform * X = self`. + /// + /// `(t2 * t1).reparented_to(t2) == t1` Note that transforms are not commutative, meaning that + /// `(t1 * t2).reparented_to(t2) != t1`. + /// + /// 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_math::{Vec3, Quat}; + /// # use bevy_transform::prelude::{GlobalTransform, Transform}; + /// # use bevy_ecs::prelude::{Entity, Query, Component, Commands}; + /// # use bevy_hierarchy::prelude::Parent; + /// #[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::() + /// .insert(Parent(to_reparent.new_parent)); + /// } + /// } + /// } + /// ``` + #[inline] + #[must_use] + pub fn reparented_to(&self, transform: GlobalTransform) -> Transform { + let (spos, srot, sscale) = (self.translation, self.rotation, self.scale); + let (tpos, trot, tscale) = (transform.translation, transform.rotation, transform.scale); + Transform { + translation: trot.inverse() * (spos - tpos) / tscale, + rotation: trot.inverse() * srot, + scale: sscale / tscale, + } + } + /// Returns a [`Vec3`] of this [`Transform`] applied to `value`. #[inline] pub fn mul_vec3(&self, mut value: Vec3) -> Vec3 { @@ -287,3 +335,37 @@ impl Mul for GlobalTransform { self.mul_vec3(value) } } + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn reparented_to_transform_identity() { + fn identity(t1: GlobalTransform, t2: GlobalTransform) -> Transform { + t2.mul_transform(t1.into()).reparented_to(t2) + } + let t1 = GlobalTransform { + translation: Vec3::new(1034.0, 34.0, -1324.34), + rotation: Quat::from_rotation_x(1.2), + scale: Vec3::new(1.0, 2.345, 0.9), + }; + let t2 = GlobalTransform { + translation: Vec3::new(28.0, -54.493, 324.34), + rotation: Quat::from_rotation_z(1.9), + scale: Vec3::new(3.0, 1.345, 0.9), + }; + let f32_equal = |left: f32, right: f32| (left - right).abs() < 0.0001; + let rt_t1_pos = identity(t1, t2).translation; + let rt_t2_pos = identity(t2, t1).translation; + assert!(f32_equal(t1.translation.length(), rt_t1_pos.length())); + assert!(f32_equal(t2.translation.length(), rt_t2_pos.length())); + assert!(f32_equal( + t1.scale.length(), + identity(t1, t2).scale.length() + )); + assert!(f32_equal( + t2.scale.length(), + identity(t2, t1).scale.length() + )); + } +} diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index bbca98e76961d..97ca8fec5e854 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -210,7 +210,11 @@ impl Transform { } /// Multiplies `self` with `transform` component by component, returning the - /// resulting [`Transform`] + /// resulting [`Transform`]. + /// + /// Note that `self.mul_transform(transform)` is identical to `self * transform`. + /// + /// To find `X` such as `transform * X = self`, see [`GlobalTransform::reparented_to`]. #[inline] #[must_use] pub fn mul_transform(&self, transform: Transform) -> Self { From f277b46f73b24d35e533c9675a5a8fb3a0eec2d1 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Sun, 3 Jul 2022 11:43:29 +0200 Subject: [PATCH 2/2] Add reparented_to tests --- .../src/components/global_transform.rs | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index 7601599c48e3a..5d32d44ffea7a 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -339,33 +339,54 @@ impl Mul for GlobalTransform { #[cfg(test)] mod test { use super::*; + + use bevy_math::EulerRot::XYZ; + + fn transform_equal(left: GlobalTransform, right: Transform) -> bool { + left.scale.abs_diff_eq(right.scale, 0.001) + && left.translation.abs_diff_eq(right.translation, 0.001) + && left.rotation.angle_between(right.rotation) < 0.0001 + } + #[test] fn reparented_to_transform_identity() { - fn identity(t1: GlobalTransform, t2: GlobalTransform) -> Transform { + fn reparent_to_same(t1: GlobalTransform, t2: GlobalTransform) -> Transform { t2.mul_transform(t1.into()).reparented_to(t2) } let t1 = GlobalTransform { translation: Vec3::new(1034.0, 34.0, -1324.34), - rotation: Quat::from_rotation_x(1.2), - scale: Vec3::new(1.0, 2.345, 0.9), + rotation: Quat::from_euler(XYZ, 1.0, 0.9, 2.1), + scale: Vec3::new(1.0, 2.345, 0.0), }; let t2 = GlobalTransform { - translation: Vec3::new(28.0, -54.493, 324.34), - rotation: Quat::from_rotation_z(1.9), + translation: Vec3::new(0.0, -54.493, 324.34), + rotation: Quat::from_euler(XYZ, 1.9, 0.3, 3.0), scale: Vec3::new(3.0, 1.345, 0.9), }; - let f32_equal = |left: f32, right: f32| (left - right).abs() < 0.0001; - let rt_t1_pos = identity(t1, t2).translation; - let rt_t2_pos = identity(t2, t1).translation; - assert!(f32_equal(t1.translation.length(), rt_t1_pos.length())); - assert!(f32_equal(t2.translation.length(), rt_t2_pos.length())); - assert!(f32_equal( - t1.scale.length(), - identity(t1, t2).scale.length() - )); - assert!(f32_equal( - t2.scale.length(), - identity(t2, t1).scale.length() - )); + let retransformed = reparent_to_same(t1, t2); + assert!( + transform_equal(t1, retransformed), + "t1:{t1:#?} retransformed:{retransformed:#?}" + ); + } + #[test] + fn reparented_usecase() { + let t1 = GlobalTransform { + translation: Vec3::new(1034.0, 34.0, -1324.34), + rotation: Quat::from_euler(XYZ, 0.8, 1.9, 2.1), + scale: Vec3::new(-1.0, -2.3, 10.9), + }; + let t2 = GlobalTransform { + translation: Vec3::new(28.0, -54.493, 324.34), + rotation: Quat::from_euler(XYZ, 0.0, 3.1, 0.1), + scale: Vec3::new(3.0, -1.345, 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:#?} t1_prime:{t1_prime:#?}" + ); } }