From 689eab6fb7028e3da2d0e01b8e2d8b26f59c92cb Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Thu, 12 Jan 2023 18:46:11 +0000 Subject: [PATCH] Add an extension trait to `EntityCommands` to update hierarchy while preserving `GlobalTransform` (#7024) # 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 2, resolves #5475 - Builds on: #7020. ## Solution - Added the `BuildChildrenTransformExt` trait, it is part of `bevy::prelude` and adds the following methods to `EntityCommands`: - `set_parent_in_place`: Change the parent of an entity and update its `Transform` in order to preserve its `GlobalTransform` after the parent change - `remove_parent_in_place`: Remove an entity from a hierarchy, while preserving its `GlobalTransform`. --- ## Changelog - Added the `BuildChildrenTransformExt` trait, it is part of `bevy::prelude` and adds the following methods to `EntityCommands`: - `set_parent_in_place`: Change the parent of an entity and update its `Transform` in order to preserve its `GlobalTransform` after the parent change - `remove_parent_in_place`: Remove an entity from a hierarchy, while preserving its `GlobalTransform`. Co-authored-by: Nicola Papale --- crates/bevy_hierarchy/src/child_builder.rs | 3 +- crates/bevy_transform/src/commands.rs | 101 +++++++++++++++++++++ crates/bevy_transform/src/lib.rs | 5 +- 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 crates/bevy_transform/src/commands.rs diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index 9e2c328348..31970ee4c0 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -198,7 +198,8 @@ impl Command for RemoveChildren { /// Command that removes the parent of an entity, and removes that entity from the parent's [`Children`]. pub struct RemoveParent { - child: Entity, + /// `Entity` whose parent must be removed. + pub child: Entity, } impl Command for RemoveParent { diff --git a/crates/bevy_transform/src/commands.rs b/crates/bevy_transform/src/commands.rs new file mode 100644 index 0000000000..0fa7234ca0 --- /dev/null +++ b/crates/bevy_transform/src/commands.rs @@ -0,0 +1,101 @@ +//! Extension to [`EntityCommands`] to modify [`bevy_hierarchy`] hierarchies +//! while preserving [`GlobalTransform`]. + +use bevy_ecs::{prelude::Entity, system::Command, system::EntityCommands, world::World}; +use bevy_hierarchy::{AddChild, RemoveParent}; + +#[cfg(doc)] +use bevy_hierarchy::BuildChildren; + +use crate::{GlobalTransform, Transform}; + +/// Command similar to [`AddChild`], but updating the child transform to keep +/// it at the same [`GlobalTransform`]. +/// +/// You most likely want to use [`BuildChildrenTransformExt::set_parent_in_place`] +/// method on [`EntityCommands`] instead. +pub struct AddChildInPlace { + /// Parent entity to add the child to. + pub parent: Entity, + /// Child entity to add. + pub child: Entity, +} +impl Command for AddChildInPlace { + fn write(self, world: &mut World) { + let hierarchy_command = AddChild { + child: self.child, + parent: self.parent, + }; + hierarchy_command.write(world); + // FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436. + let mut update_transform = || { + let parent = *world.get_entity(self.parent)?.get::()?; + let child_global = *world.get_entity(self.child)?.get::()?; + let mut child_entity = world.get_entity_mut(self.child)?; + let mut child = child_entity.get_mut::()?; + *child = child_global.reparented_to(&parent); + Some(()) + }; + update_transform(); + } +} +/// Command similar to [`RemoveParent`], but updating the child transform to keep +/// it at the same [`GlobalTransform`]. +/// +/// You most likely want to use [`BuildChildrenTransformExt::remove_parent_in_place`] +/// method on [`EntityCommands`] instead. +pub struct RemoveParentInPlace { + /// `Entity` whose parent must be removed. + pub child: Entity, +} +impl Command for RemoveParentInPlace { + fn write(self, world: &mut World) { + let hierarchy_command = RemoveParent { child: self.child }; + hierarchy_command.write(world); + // FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436. + let mut update_transform = || { + let child_global = *world.get_entity(self.child)?.get::()?; + let mut child_entity = world.get_entity_mut(self.child)?; + let mut child = child_entity.get_mut::()?; + *child = child_global.compute_transform(); + Some(()) + }; + update_transform(); + } +} +/// Collection of methods similar to [`BuildChildren`], but preserving each +/// entity's [`GlobalTransform`]. +pub trait BuildChildrenTransformExt { + /// Change this entity's parent while preserving this entity's [`GlobalTransform`] + /// by updating its [`Transform`]. + /// + /// See [`BuildChildren::set_parent`] for a method that doesn't update the + /// [`Transform`]. + /// + /// Note that both the hierarchy and transform updates will only execute + /// at the end of the current stage. + fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self; + + /// Make this entity parentless while preserving this entity's [`GlobalTransform`] + /// by updating its [`Transform`] to be equal to its current [`GlobalTransform`]. + /// + /// See [`BuildChildren::remove_parent`] for a method that doesn't update the + /// [`Transform`]. + /// + /// Note that both the hierarchy and transform updates will only execute + /// at the end of the current stage. + fn remove_parent_in_place(&mut self) -> &mut Self; +} +impl<'w, 's, 'a> BuildChildrenTransformExt for EntityCommands<'w, 's, 'a> { + fn remove_parent_in_place(&mut self) -> &mut Self { + let child = self.id(); + self.commands().add(RemoveParentInPlace { child }); + self + } + + fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self { + let child = self.id(); + self.commands().add(AddChildInPlace { child, parent }); + self + } +} diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index 1fa1ea1c54..c68f01b2e0 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -2,6 +2,7 @@ #![warn(clippy::undocumented_unsafe_blocks)] #![doc = include_str!("../README.md")] +pub mod commands; /// The basic components of the transform crate pub mod components; mod systems; @@ -9,7 +10,9 @@ mod systems; #[doc(hidden)] pub mod prelude { #[doc(hidden)] - pub use crate::{components::*, TransformBundle, TransformPlugin}; + pub use crate::{ + commands::BuildChildrenTransformExt, components::*, TransformBundle, TransformPlugin, + }; } use bevy_app::prelude::*;