Skip to content

Commit

Permalink
Add a reparented_to method to GlobalTransform (#7020)
Browse files Browse the repository at this point in the history
# 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`
  • Loading branch information
nicopap committed Dec 25, 2022
1 parent 48b4a45 commit b8a9933
Showing 1 changed file with 101 additions and 0 deletions.
101 changes: 101 additions & 0 deletions crates/bevy_transform/src/components/global_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
);
}
}

0 comments on commit b8a9933

Please sign in to comment.