From d1496297216041550d2c9f6040b1bfe48772b902 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Sat, 24 Dec 2022 15:28:16 +0100 Subject: [PATCH 1/8] Add hierarchy update commands keeping transform --- crates/bevy_hierarchy/src/child_builder.rs | 3 +- crates/bevy_transform/src/commands.rs | 96 ++++++++++++++++++++++ crates/bevy_transform/src/lib.rs | 6 +- 3 files changed, 103 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 9e2c328348d04..2c36eb63bab9f 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` which 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 0000000000000..47532046659ab --- /dev/null +++ b/crates/bevy_transform/src/commands.rs @@ -0,0 +1,96 @@ +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_keep_global_transform`] +/// method on [`EntityCommands`] instead. +pub struct AddChildKeep { + /// Parent entity to add the child to. + pub parent: Entity, + /// Child entity to add. + pub child: Entity, +} +impl Command for AddChildKeep { + fn write(self, world: &mut World) { + let hierarchy_command = AddChild { + child: self.child, + parent: self.parent, + }; + hierarchy_command.write(world); + 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_keep_global_transform`] +/// method on [`EntityCommands`] instead. +struct RemoveParentKeep { + /// `Entity` which parent must be removed. + child: Entity, +} +impl Command for RemoveParentKeep { + fn write(self, world: &mut World) { + let hierarchy_command = RemoveParent { child: self.child }; + hierarchy_command.write(world); + 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_keep_global_transform(&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_keep_global_transform(&mut self) -> &mut Self; +} +impl<'w, 's, 'a> BuildChildrenTransformExt for EntityCommands<'w, 's, 'a> { + fn remove_parent_keep_global_transform(&mut self) -> &mut Self { + let child = self.id(); + self.commands().add(RemoveParentKeep { child }); + self + } + + fn set_parent_keep_global_transform(&mut self, parent: Entity) -> &mut Self { + let child = self.id(); + self.commands().add(AddChildKeep { child, parent }); + self + } +} diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index 0c09757573aeb..4b049a9787aa1 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -2,6 +2,8 @@ #![warn(clippy::undocumented_unsafe_blocks)] #![doc = include_str!("../README.md")] +/// Extension to [`EntityCommands`] to change hierarchy while preserving [`GlobalTransform`]. +pub mod commands; /// The basic components of the transform crate pub mod components; mod systems; @@ -9,7 +11,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::*; From 5e6311ef8600a216d3e4528f1fe45aae904d69f5 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Sat, 24 Dec 2022 15:35:53 +0100 Subject: [PATCH 2/8] Fix doc warning --- crates/bevy_transform/src/commands.rs | 2 ++ crates/bevy_transform/src/lib.rs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_transform/src/commands.rs b/crates/bevy_transform/src/commands.rs index 47532046659ab..0f3c4fa941956 100644 --- a/crates/bevy_transform/src/commands.rs +++ b/crates/bevy_transform/src/commands.rs @@ -1,3 +1,5 @@ +//! Extension to [`EntityCommands`] to change hierarchy while preserving [`GlobalTransform`]. + use bevy_ecs::{prelude::Entity, system::Command, system::EntityCommands, world::World}; use bevy_hierarchy::{AddChild, RemoveParent}; diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index 4b049a9787aa1..db0cb93665783 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -2,7 +2,6 @@ #![warn(clippy::undocumented_unsafe_blocks)] #![doc = include_str!("../README.md")] -/// Extension to [`EntityCommands`] to change hierarchy while preserving [`GlobalTransform`]. pub mod commands; /// The basic components of the transform crate pub mod components; From 0941d203ad123cc9236f62dd688f1fab3a0618da Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Sat, 7 Jan 2023 20:54:24 +0100 Subject: [PATCH 3/8] Rename 'keep_global_transform' to 'in_place' --- crates/bevy_transform/src/commands.rs | 20 +- .../transforms/global_vs_local_translation.rs | 235 ++++++++++++++++++ 2 files changed, 245 insertions(+), 10 deletions(-) create mode 100644 examples/transforms/global_vs_local_translation.rs diff --git a/crates/bevy_transform/src/commands.rs b/crates/bevy_transform/src/commands.rs index 0f3c4fa941956..453371270a902 100644 --- a/crates/bevy_transform/src/commands.rs +++ b/crates/bevy_transform/src/commands.rs @@ -13,13 +13,13 @@ use crate::{GlobalTransform, Transform}; /// /// You most likely want to use [`BuildChildrenTransformExt::set_parent_keep_global_transform`] /// method on [`EntityCommands`] instead. -pub struct AddChildKeep { +pub struct AddChildInPlace { /// Parent entity to add the child to. pub parent: Entity, /// Child entity to add. pub child: Entity, } -impl Command for AddChildKeep { +impl Command for AddChildInPlace { fn write(self, world: &mut World) { let hierarchy_command = AddChild { child: self.child, @@ -42,11 +42,11 @@ impl Command for AddChildKeep { /// /// You most likely want to use [`BuildChildrenTransformExt::remove_parent_keep_global_transform`] /// method on [`EntityCommands`] instead. -struct RemoveParentKeep { +struct RemoveParentInPlace { /// `Entity` which parent must be removed. child: Entity, } -impl Command for RemoveParentKeep { +impl Command for RemoveParentInPlace { fn write(self, world: &mut World) { let hierarchy_command = RemoveParent { child: self.child }; hierarchy_command.write(world); @@ -71,7 +71,7 @@ pub trait BuildChildrenTransformExt { /// /// Note that both the hierarchy and transform updates will only execute /// at the end of the current stage. - fn set_parent_keep_global_transform(&mut self, parent: Entity) -> &mut Self; + 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`]. @@ -81,18 +81,18 @@ pub trait BuildChildrenTransformExt { /// /// Note that both the hierarchy and transform updates will only execute /// at the end of the current stage. - fn remove_parent_keep_global_transform(&mut self) -> &mut Self; + fn remove_parent_in_place(&mut self) -> &mut Self; } impl<'w, 's, 'a> BuildChildrenTransformExt for EntityCommands<'w, 's, 'a> { - fn remove_parent_keep_global_transform(&mut self) -> &mut Self { + fn remove_parent_in_place(&mut self) -> &mut Self { let child = self.id(); - self.commands().add(RemoveParentKeep { child }); + self.commands().add(RemoveParentInPlace { child }); self } - fn set_parent_keep_global_transform(&mut self, parent: Entity) -> &mut Self { + fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self { let child = self.id(); - self.commands().add(AddChildKeep { child, parent }); + self.commands().add(AddChildInPlace { child, parent }); self } } diff --git a/examples/transforms/global_vs_local_translation.rs b/examples/transforms/global_vs_local_translation.rs new file mode 100644 index 0000000000000..3985f2344e276 --- /dev/null +++ b/examples/transforms/global_vs_local_translation.rs @@ -0,0 +1,235 @@ +//! Illustrates the difference between direction of a translation in respect to local object or +//! global object Transform. + +use bevy::{math::Vec3A, prelude::*}; + +// Define a marker for entities that should be changed via their global transform. +#[derive(Component)] +struct ChangeGlobal; + +// Define a marker for entities that should be changed via their local transform. +#[derive(Component)] +struct ChangeLocal; + +// Define a marker for entities that should move. +#[derive(Component)] +struct Move; + +// Define a resource for the current movement direction; +#[derive(Resource, Default)] +struct Direction(Vec3); + +// Define component to decide when an entity should be ignored by the movement systems. +#[derive(Component)] +struct ToggledBy(KeyCode); + +#[derive(Resource)] +struct DynamicParent { + green: Entity, + yellow: Entity, + has_hierarchy: bool, +} + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_startup_system(setup) + .init_resource::() + .add_system(move_cubes_according_to_global_transform) + .add_system(move_cubes_according_to_local_transform) + .add_system(update_directional_input) + .add_system(toggle_movement) + .run(); +} + +// Startup system to setup the scene and spawn all relevant entities. +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + asset_server: Res, +) { + let mut green = None; + // To show the difference between a local transform (rotation, scale and + // position in respect to a given entity) and global transform (rotation, + // scale and position in respect to the base coordinate system of the visible scene) + // it's helpful to add multiple entities that are attached to each other. + // This way we'll see that the transform in respect to an entity's parent is different to the + // global transform within the visible scene. + // This example focuses on translation only to clearly demonstrate the differences. + + // Spawn a basic cube to have an entity as reference. + let yellow = commands + .spawn(( + PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + material: materials.add(StandardMaterial { + base_color: Color::YELLOW, + alpha_mode: AlphaMode::Blend, + ..Default::default() + }), + ..default() + }, + ChangeGlobal, + Move, + ToggledBy(KeyCode::Key1), + )) + // Spawn two entities as children above the original main entity. + // The red entity spawned here will be changed via its global transform + // where the green one will be changed via its local transform. + .with_children(|child_builder| { + // also see parenting example + child_builder.spawn(( + PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })), + material: materials.add(StandardMaterial { + base_color: Color::RED, + alpha_mode: AlphaMode::Blend, + ..Default::default() + }), + transform: Transform::from_translation(Vec3::Y - Vec3::Z), + ..default() + }, + ChangeGlobal, + Move, + ToggledBy(KeyCode::Key2), + )); + let green_content = child_builder + .spawn(( + PbrBundle { + mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })), + material: materials.add(StandardMaterial { + base_color: Color::GREEN, + alpha_mode: AlphaMode::Blend, + ..Default::default() + }), + transform: Transform::from_translation(Vec3::Y + Vec3::Z), + ..default() + }, + ChangeLocal, + Move, + ToggledBy(KeyCode::Key3), + )) + .id(); + green = Some(green_content); + }) + .id(); + commands.insert_resource(DynamicParent { + green: green.unwrap(), + yellow, + has_hierarchy: true, + }); + + // Spawn a camera looking at the entities to show what's happening in this example. + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(0.0, 10.0, 20.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); + + // Add a light source for better 3d visibility. + commands.spawn(PointLightBundle { + transform: Transform::from_translation(Vec3::splat(3.0)), + ..default() + }); + + // Add text to explain inputs and what is happening. + commands.spawn(TextBundle::from_section( + "Press the arrow keys to move the cubes. \ + Toggle movement for yellow (1), red (2) and green (3) cubes via number keys.\n\n\ + Notice how the green cube will translate further in respect to the \ + yellow in contrast to the red cube.\n\ + This is due to the use of its LocalTransform that is relative to the \ + yellow cubes transform instead of the GlobalTransform as in the case of the red cube.\n\ + The red cube is moved through its GlobalTransform and thus is \ + unaffected by the yellows transform.\n\ + You can toggle the parent relationship between the green and yellow cubes using (4)", + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 22.0, + color: Color::WHITE, + }, + )); +} + +// This system will move all cubes that are marked as ChangeGlobal according to their global transform. +fn move_cubes_according_to_global_transform( + mut cubes: Query<&mut GlobalTransform, (With, With)>, + direction: Res, + time: Res