Add a reparented_to method to GlobalTransform (#7020)

# Objective

It is often necessary  to update an entity's parent while keeping its GlobalTransform static. Currently it is cumbersome and error-prone (two questions in the discord `#help` channel in the past week)

- Part 1 of #5475
- Part 2: #7024.

## Solution

- Add a `reparented_to` method to `GlobalTransform`

---

## Changelog

- Add a `reparented_to` method to `GlobalTransform`
This commit is contained in:
Nicola Papale 2022-12-25 00:51:20 +00:00
parent 48b4a45d82
commit b8a9933d46

View File

@ -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::<ToReparent>()
/// .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<Vec3> 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(),
);
}
}