Make Transform propagation correct in the presence of updated children (#4608)
				
					
				
			Supercedes https://github.com/bevyengine/bevy/pull/3340, and absorbs the test from there. # Objective - Fixes #3329 ## Solution - If the `Children` component has changed, we currently do not have a way to know how it has changed. - Therefore, we must update the hierarchy downwards from that point to be correct. Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									4c194084b4
								
							
						
					
					
						commit
						a011f4d53c
					
				@ -1,10 +1,5 @@
 | 
				
			|||||||
use crate::components::{GlobalTransform, Transform};
 | 
					use crate::components::{GlobalTransform, Transform};
 | 
				
			||||||
use bevy_ecs::{
 | 
					use bevy_ecs::prelude::{Changed, Entity, Query, With, Without};
 | 
				
			||||||
    entity::Entity,
 | 
					 | 
				
			||||||
    prelude::Changed,
 | 
					 | 
				
			||||||
    query::{With, Without},
 | 
					 | 
				
			||||||
    system::Query,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use bevy_hierarchy::{Children, Parent};
 | 
					use bevy_hierarchy::{Children, Parent};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Update [`GlobalTransform`] component of entities based on entity hierarchy and
 | 
					/// Update [`GlobalTransform`] component of entities based on entity hierarchy and
 | 
				
			||||||
@ -12,7 +7,7 @@ use bevy_hierarchy::{Children, Parent};
 | 
				
			|||||||
pub fn transform_propagate_system(
 | 
					pub fn transform_propagate_system(
 | 
				
			||||||
    mut root_query: Query<
 | 
					    mut root_query: Query<
 | 
				
			||||||
        (
 | 
					        (
 | 
				
			||||||
            Option<&Children>,
 | 
					            Option<(&Children, Changed<Children>)>,
 | 
				
			||||||
            &Transform,
 | 
					            &Transform,
 | 
				
			||||||
            Changed<Transform>,
 | 
					            Changed<Transform>,
 | 
				
			||||||
            &mut GlobalTransform,
 | 
					            &mut GlobalTransform,
 | 
				
			||||||
@ -23,18 +18,19 @@ pub fn transform_propagate_system(
 | 
				
			|||||||
        (&Transform, Changed<Transform>, &mut GlobalTransform),
 | 
					        (&Transform, Changed<Transform>, &mut GlobalTransform),
 | 
				
			||||||
        With<Parent>,
 | 
					        With<Parent>,
 | 
				
			||||||
    >,
 | 
					    >,
 | 
				
			||||||
    children_query: Query<Option<&Children>, (With<Parent>, With<GlobalTransform>)>,
 | 
					    children_query: Query<(&Children, Changed<Children>), (With<Parent>, With<GlobalTransform>)>,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    for (children, transform, transform_changed, mut global_transform) in root_query.iter_mut() {
 | 
					    for (children, transform, transform_changed, mut global_transform) in root_query.iter_mut() {
 | 
				
			||||||
        let mut changed = false;
 | 
					        let mut changed = transform_changed;
 | 
				
			||||||
        if transform_changed {
 | 
					        if transform_changed {
 | 
				
			||||||
            *global_transform = GlobalTransform::from(*transform);
 | 
					            *global_transform = GlobalTransform::from(*transform);
 | 
				
			||||||
            changed = true;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(children) = children {
 | 
					        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() {
 | 
					            for child in children.iter() {
 | 
				
			||||||
                propagate_recursive(
 | 
					                let _ = propagate_recursive(
 | 
				
			||||||
                    &global_transform,
 | 
					                    &global_transform,
 | 
				
			||||||
                    &mut transform_query,
 | 
					                    &mut transform_query,
 | 
				
			||||||
                    &children_query,
 | 
					                    &children_query,
 | 
				
			||||||
@ -52,27 +48,27 @@ fn propagate_recursive(
 | 
				
			|||||||
        (&Transform, Changed<Transform>, &mut GlobalTransform),
 | 
					        (&Transform, Changed<Transform>, &mut GlobalTransform),
 | 
				
			||||||
        With<Parent>,
 | 
					        With<Parent>,
 | 
				
			||||||
    >,
 | 
					    >,
 | 
				
			||||||
    children_query: &Query<Option<&Children>, (With<Parent>, With<GlobalTransform>)>,
 | 
					    children_query: &Query<(&Children, Changed<Children>), (With<Parent>, With<GlobalTransform>)>,
 | 
				
			||||||
    entity: Entity,
 | 
					    entity: Entity,
 | 
				
			||||||
    mut changed: bool,
 | 
					    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 global_matrix = {
 | 
				
			||||||
        if let Ok((transform, transform_changed, mut global_transform)) =
 | 
					        let (transform, transform_changed, mut global_transform) =
 | 
				
			||||||
            transform_query.get_mut(entity)
 | 
					            transform_query.get_mut(entity).map_err(drop)?;
 | 
				
			||||||
        {
 | 
					
 | 
				
			||||||
        changed |= transform_changed;
 | 
					        changed |= transform_changed;
 | 
				
			||||||
        if changed {
 | 
					        if changed {
 | 
				
			||||||
            *global_transform = parent.mul_transform(*transform);
 | 
					            *global_transform = parent.mul_transform(*transform);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        *global_transform
 | 
					        *global_transform
 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Ok(Some(children)) = children_query.get(entity) {
 | 
					    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() {
 | 
					    for child in children.iter() {
 | 
				
			||||||
            propagate_recursive(
 | 
					        let _ = propagate_recursive(
 | 
				
			||||||
            &global_matrix,
 | 
					            &global_matrix,
 | 
				
			||||||
            transform_query,
 | 
					            transform_query,
 | 
				
			||||||
            children_query,
 | 
					            children_query,
 | 
				
			||||||
@ -80,16 +76,15 @@ fn propagate_recursive(
 | 
				
			|||||||
            changed,
 | 
					            changed,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    }
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod test {
 | 
					mod test {
 | 
				
			||||||
    use bevy_ecs::{
 | 
					    use bevy_app::prelude::*;
 | 
				
			||||||
        schedule::{Schedule, Stage, SystemStage},
 | 
					    use bevy_ecs::prelude::*;
 | 
				
			||||||
        system::{CommandQueue, Commands},
 | 
					    use bevy_ecs::system::CommandQueue;
 | 
				
			||||||
        world::World,
 | 
					    use bevy_math::vec3;
 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use crate::components::{GlobalTransform, Transform};
 | 
					    use crate::components::{GlobalTransform, Transform};
 | 
				
			||||||
    use crate::systems::transform_propagate_system;
 | 
					    use crate::systems::transform_propagate_system;
 | 
				
			||||||
@ -271,4 +266,60 @@ mod test {
 | 
				
			|||||||
            vec![children[1]]
 | 
					            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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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::<Children>(parent).unwrap(), &[child]);
 | 
				
			||||||
 | 
					        assert_eq!(&**app.world.get::<Children>(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()
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user