Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add a reparented_to method to GlobalTransform #7020

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(),
);
}
}