use crate::components::{GlobalTransform, Transform}; use bevy_ecs::prelude::{Changed, Entity, Query, With, Without}; use bevy_hierarchy::{Children, Parent}; /// Update [`GlobalTransform`] component of entities based on entity hierarchy and /// [`Transform`] component. pub fn transform_propagate_system( mut root_query: Query< ( Option<(&Children, Changed)>, &Transform, Changed, &mut GlobalTransform, Entity, ), Without, >, mut transform_query: Query<( &Transform, Changed, &mut GlobalTransform, &Parent, )>, children_query: Query<(&Children, Changed), (With, With)>, ) { for (children, transform, transform_changed, mut global_transform, entity) in root_query.iter_mut() { let mut changed = transform_changed; if transform_changed { *global_transform = GlobalTransform::from(*transform); } if let Some((children, changed_children)) = children { // If our `Children` has changed, we need to recalculate everything below us changed |= changed_children; for child in children.iter() { let _ = propagate_recursive( &global_transform, &mut transform_query, &children_query, *child, entity, changed, ); } } } } fn propagate_recursive( parent: &GlobalTransform, transform_query: &mut Query<( &Transform, Changed, &mut GlobalTransform, &Parent, )>, children_query: &Query<(&Children, Changed), (With, With)>, entity: Entity, expected_parent: Entity, mut changed: bool, // We use a result here to use the `?` operator. Ideally we'd use a try block instead ) -> Result<(), ()> { let global_matrix = { let (transform, transform_changed, mut global_transform, child_parent) = transform_query.get_mut(entity).map_err(drop)?; // Note that for parallelising, this check cannot occur here, since there is an `&mut GlobalTransform` (in global_transform) assert_eq!( child_parent.0, expected_parent, "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle" ); changed |= transform_changed; if changed { *global_transform = parent.mul_transform(*transform); } *global_transform }; let (children, changed_children) = children_query.get(entity).map_err(drop)?; // If our `Children` has changed, we need to recalculate everything below us changed |= changed_children; for child in children.iter() { let _ = propagate_recursive( &global_matrix, transform_query, children_query, *child, entity, changed, ); } Ok(()) } #[cfg(test)] mod test { use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_ecs::system::CommandQueue; use bevy_math::vec3; use crate::components::{GlobalTransform, Transform}; use crate::systems::transform_propagate_system; use crate::TransformBundle; use bevy_hierarchy::{ parent_update_system, BuildChildren, BuildWorldChildren, Children, Parent, }; #[test] fn did_propagate() { let mut world = World::default(); let mut update_stage = SystemStage::parallel(); update_stage.add_system(parent_update_system); update_stage.add_system(transform_propagate_system.after(parent_update_system)); let mut schedule = Schedule::default(); schedule.add_stage("update", update_stage); // Root entity world .spawn() .insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))); let mut children = Vec::new(); world .spawn() .insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))) .with_children(|parent| { children.push( parent .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.))) .id(), ); children.push( parent .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.))) .id(), ); }); schedule.run(&mut world); assert_eq!( *world.get::(children[0]).unwrap(), GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0) ); assert_eq!( *world.get::(children[1]).unwrap(), GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0) ); } #[test] fn did_propagate_command_buffer() { let mut world = World::default(); let mut update_stage = SystemStage::parallel(); update_stage.add_system(parent_update_system); update_stage.add_system(transform_propagate_system.after(parent_update_system)); let mut schedule = Schedule::default(); schedule.add_stage("update", update_stage); // Root entity let mut queue = CommandQueue::default(); let mut commands = Commands::new(&mut queue, &world); let mut children = Vec::new(); commands .spawn_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))) .with_children(|parent| { children.push( parent .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.0))) .id(), ); children.push( parent .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.0))) .id(), ); }); queue.apply(&mut world); schedule.run(&mut world); assert_eq!( *world.get::(children[0]).unwrap(), GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0) ); assert_eq!( *world.get::(children[1]).unwrap(), GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0) ); } #[test] fn correct_children() { let mut world = World::default(); let mut update_stage = SystemStage::parallel(); update_stage.add_system(parent_update_system); update_stage.add_system(transform_propagate_system.after(parent_update_system)); let mut schedule = Schedule::default(); schedule.add_stage("update", update_stage); // Add parent entities let mut command_queue = CommandQueue::default(); let mut commands = Commands::new(&mut command_queue, &world); let mut children = Vec::new(); let parent = commands .spawn() .insert(Transform::from_xyz(1.0, 0.0, 0.0)) .id(); commands.entity(parent).with_children(|parent| { children.push( parent .spawn() .insert(Transform::from_xyz(0.0, 2.0, 0.0)) .id(), ); children.push( parent .spawn() .insert(Transform::from_xyz(0.0, 3.0, 0.0)) .id(), ); }); command_queue.apply(&mut world); schedule.run(&mut world); assert_eq!( world .get::(parent) .unwrap() .iter() .cloned() .collect::>(), children, ); // Parent `e1` to `e2`. (*world.get_mut::(children[0]).unwrap()).0 = children[1]; schedule.run(&mut world); assert_eq!( world .get::(parent) .unwrap() .iter() .cloned() .collect::>(), vec![children[1]] ); assert_eq!( world .get::(children[1]) .unwrap() .iter() .cloned() .collect::>(), vec![children[0]] ); assert!(world.despawn(children[0])); schedule.run(&mut world); assert_eq!( world .get::(parent) .unwrap() .iter() .cloned() .collect::>(), vec![children[1]] ); } #[test] fn correct_transforms_when_no_children() { let mut app = App::new(); app.add_system(parent_update_system); app.add_system(transform_propagate_system.after(parent_update_system)); let translation = vec3(1.0, 0.0, 0.0); let parent = app .world .spawn() .insert(Transform::from_translation(translation)) .insert(GlobalTransform::default()) .id(); let child = app .world .spawn() .insert_bundle(( Transform::identity(), GlobalTransform::default(), Parent(parent), )) .id(); let grandchild = app .world .spawn() .insert_bundle(( Transform::identity(), GlobalTransform::default(), Parent(child), )) .id(); app.update(); // check the `Children` structure is spawned assert_eq!(&**app.world.get::(parent).unwrap(), &[child]); assert_eq!(&**app.world.get::(child).unwrap(), &[grandchild]); // Note that at this point, the `GlobalTransform`s will not have updated yet, due to `Commands` delay app.update(); let mut state = app.world.query::<&GlobalTransform>(); for global in state.iter(&app.world) { assert_eq!( global, &GlobalTransform { translation, ..Default::default() }, ); } } #[test] #[should_panic] fn panic_when_hierarchy_cycle() { let mut world = World::default(); // This test is run on a single thread in order to avoid breaking the global task pool by panicking // This fixes the flaky tests reported in https://github.com/bevyengine/bevy/issues/4996 let mut update_stage = SystemStage::single_threaded(); update_stage.add_system(parent_update_system); update_stage.add_system(transform_propagate_system.after(parent_update_system)); let child = world .spawn() .insert_bundle((Transform::identity(), GlobalTransform::default())) .id(); let grandchild = world .spawn() .insert_bundle(( Transform::identity(), GlobalTransform::default(), Parent(child), )) .id(); world.spawn().insert_bundle(( Transform::default(), GlobalTransform::default(), Children::with(&[child]), )); world.entity_mut(child).insert(Parent(grandchild)); update_stage.run(&mut world); } }