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 { 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.get(), 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 { 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::{BuildChildren, BuildWorldChildren, Children, Parent}; #[test] fn did_propagate() { let mut world = World::default(); let mut update_stage = SystemStage::parallel(); update_stage.add_system(transform_propagate_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(transform_propagate_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(transform_propagate_system); let mut schedule = Schedule::default(); schedule.add_stage("update", update_stage); // Add parent entities let mut children = Vec::new(); let parent = { let mut command_queue = CommandQueue::default(); let mut commands = Commands::new(&mut command_queue, &world); 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); parent }; assert_eq!( world .get::(parent) .unwrap() .iter() .cloned() .collect::>(), children, ); // Parent `e1` to `e2`. { let mut command_queue = CommandQueue::default(); let mut commands = Commands::new(&mut command_queue, &world); commands.entity(children[1]).add_child(children[0]); command_queue.apply(&mut world); 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(transform_propagate_system); let translation = vec3(1.0, 0.0, 0.0); // These will be overwritten. let mut child = Entity::from_raw(0); let mut grandchild = Entity::from_raw(1); let parent = app .world .spawn() .insert(Transform::from_translation(translation)) .insert(GlobalTransform::IDENTITY) .with_children(|builder| { child = builder .spawn_bundle(TransformBundle::IDENTITY) .with_children(|builder| { grandchild = builder.spawn_bundle(TransformBundle::IDENTITY).id(); }) .id(); }) .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::from_translation(translation)); } } #[test] #[should_panic] fn panic_when_hierarchy_cycle() { // We cannot directly edit Parent and Children, so we use a temp world to break // the hierarchy's invariants. let mut temp = World::new(); let mut app = App::new(); app.add_system(transform_propagate_system); fn setup_world(world: &mut World) -> (Entity, Entity) { let mut grandchild = Entity::from_raw(0); let child = world .spawn() .insert_bundle(TransformBundle::IDENTITY) .with_children(|builder| { grandchild = builder .spawn() .insert_bundle(TransformBundle::IDENTITY) .id(); }) .id(); (child, grandchild) } let (temp_child, temp_grandchild) = setup_world(&mut temp); let (child, grandchild) = setup_world(&mut app.world); assert_eq!(temp_child, child); assert_eq!(temp_grandchild, grandchild); app.world .spawn() .insert_bundle(TransformBundle::IDENTITY) .push_children(&[child]); std::mem::swap( &mut *app.world.get_mut::(child).unwrap(), &mut *temp.get_mut::(grandchild).unwrap(), ); app.update(); } }