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:
Daniel McNab 2022-05-02 18:04:49 +00:00
parent 4c194084b4
commit a011f4d53c

View File

@ -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()
},
);
}
}
} }