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 an extension trait to EntityCommands to update hierarchy while preserving GlobalTransform #7024

Closed
Closed
Show file tree
Hide file tree
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
3 changes: 2 additions & 1 deletion crates/bevy_hierarchy/src/child_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
101 changes: 101 additions & 0 deletions crates/bevy_transform/src/commands.rs
Original file line number Diff line number Diff line change
@@ -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 = || {
nicopap marked this conversation as resolved.
Show resolved Hide resolved
let parent = *world.get_entity(self.parent)?.get::<GlobalTransform>()?;
let child_global = *world.get_entity(self.child)?.get::<GlobalTransform>()?;
let mut child_entity = world.get_entity_mut(self.child)?;
let mut child = child_entity.get_mut::<Transform>()?;
*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 = || {
nicopap marked this conversation as resolved.
Show resolved Hide resolved
let child_global = *world.get_entity(self.child)?.get::<GlobalTransform>()?;
let mut child_entity = world.get_entity_mut(self.child)?;
let mut child = child_entity.get_mut::<Transform>()?;
*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
}
}
5 changes: 4 additions & 1 deletion crates/bevy_transform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
#![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;

#[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::*;
Expand Down