Skip to content

Commit b8a9933

Browse files
committed
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`
1 parent 48b4a45 commit b8a9933

File tree

1 file changed

+101
-0
lines changed

1 file changed

+101
-0
lines changed

crates/bevy_transform/src/components/global_transform.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,50 @@ impl GlobalTransform {
106106
}
107107
}
108108

109+
/// Returns the [`Transform`] `self` would have if it was a child of an entity
110+
/// with the `parent` [`GlobalTransform`].
111+
///
112+
/// This is useful if you want to "reparent" an `Entity`. Say you have an entity
113+
/// `e1` that you want to turn into a child of `e2`, but you want `e1` to keep the
114+
/// same global transform, even after re-partenting. You would use:
115+
///
116+
/// ```rust
117+
/// # use bevy_transform::prelude::{GlobalTransform, Transform};
118+
/// # use bevy_ecs::prelude::{Entity, Query, Component, Commands};
119+
/// # use bevy_hierarchy::{prelude::Parent, BuildChildren};
120+
/// #[derive(Component)]
121+
/// struct ToReparent {
122+
/// new_parent: Entity,
123+
/// }
124+
/// fn reparent_system(
125+
/// mut commands: Commands,
126+
/// mut targets: Query<(&mut Transform, Entity, &GlobalTransform, &ToReparent)>,
127+
/// transforms: Query<&GlobalTransform>,
128+
/// ) {
129+
/// for (mut transform, entity, initial, to_reparent) in targets.iter_mut() {
130+
/// if let Ok(parent_transform) = transforms.get(to_reparent.new_parent) {
131+
/// *transform = initial.reparented_to(parent_transform);
132+
/// commands.entity(entity)
133+
/// .remove::<ToReparent>()
134+
/// .set_parent(to_reparent.new_parent);
135+
/// }
136+
/// }
137+
/// }
138+
/// ```
139+
///
140+
/// The transform is expected to be non-degenerate and without shearing, or the output
141+
/// will be invalid.
142+
#[inline]
143+
pub fn reparented_to(&self, parent: &GlobalTransform) -> Transform {
144+
let relative_affine = parent.affine().inverse() * self.affine();
145+
let (scale, rotation, translation) = relative_affine.to_scale_rotation_translation();
146+
Transform {
147+
translation,
148+
rotation,
149+
scale,
150+
}
151+
}
152+
109153
/// Extracts `scale`, `rotation` and `translation` from `self`.
110154
///
111155
/// The transform is expected to be non-degenerate and without shearing, or the output
@@ -209,3 +253,60 @@ impl Mul<Vec3> for GlobalTransform {
209253
self.transform_point(value)
210254
}
211255
}
256+
257+
#[cfg(test)]
258+
mod test {
259+
use super::*;
260+
261+
use bevy_math::EulerRot::XYZ;
262+
263+
fn transform_equal(left: GlobalTransform, right: Transform) -> bool {
264+
left.0.abs_diff_eq(right.compute_affine(), 0.01)
265+
}
266+
267+
#[test]
268+
fn reparented_to_transform_identity() {
269+
fn reparent_to_same(t1: GlobalTransform, t2: GlobalTransform) -> Transform {
270+
t2.mul_transform(t1.into()).reparented_to(&t2)
271+
}
272+
let t1 = GlobalTransform::from(Transform {
273+
translation: Vec3::new(1034.0, 34.0, -1324.34),
274+
rotation: Quat::from_euler(XYZ, 1.0, 0.9, 2.1),
275+
scale: Vec3::new(1.0, 1.0, 1.0),
276+
});
277+
let t2 = GlobalTransform::from(Transform {
278+
translation: Vec3::new(0.0, -54.493, 324.34),
279+
rotation: Quat::from_euler(XYZ, 1.9, 0.3, 3.0),
280+
scale: Vec3::new(1.345, 1.345, 1.345),
281+
});
282+
let retransformed = reparent_to_same(t1, t2);
283+
assert!(
284+
transform_equal(t1, retransformed),
285+
"t1:{:#?} retransformed:{:#?}",
286+
t1.compute_transform(),
287+
retransformed,
288+
);
289+
}
290+
#[test]
291+
fn reparented_usecase() {
292+
let t1 = GlobalTransform::from(Transform {
293+
translation: Vec3::new(1034.0, 34.0, -1324.34),
294+
rotation: Quat::from_euler(XYZ, 0.8, 1.9, 2.1),
295+
scale: Vec3::new(10.9, 10.9, 10.9),
296+
});
297+
let t2 = GlobalTransform::from(Transform {
298+
translation: Vec3::new(28.0, -54.493, 324.34),
299+
rotation: Quat::from_euler(XYZ, 0.0, 3.1, 0.1),
300+
scale: Vec3::new(0.9, 0.9, 0.9),
301+
});
302+
// goal: find `X` such as `t2 * X = t1`
303+
let reparented = t1.reparented_to(&t2);
304+
let t1_prime = t2 * reparented;
305+
assert!(
306+
transform_equal(t1, t1_prime.into()),
307+
"t1:{:#?} t1_prime:{:#?}",
308+
t1.compute_transform(),
309+
t1_prime.compute_transform(),
310+
);
311+
}
312+
}

0 commit comments

Comments
 (0)