Skip to content

Commit 96c2bbf

Browse files
nicopapvyb
andcommitted
Fix transform propagation for orphaned hierarchies
Co-authored-by: vyb <[email protected]>
1 parent f3a23fd commit 96c2bbf

File tree

1 file changed

+79
-9
lines changed

1 file changed

+79
-9
lines changed

crates/bevy_transform/src/systems.rs

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,38 @@ use crate::components::{GlobalTransform, Transform};
22
use bevy_ecs::{
33
change_detection::Ref,
44
prelude::{Changed, DetectChanges, Entity, Query, With, Without},
5+
removal_detection::RemovedComponents,
6+
system::ParamSet,
57
};
68
use bevy_hierarchy::{Children, Parent};
79

810
/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy
911
///
1012
/// Third party plugins should ensure that this is used in concert with [`propagate_transforms`].
1113
pub fn sync_simple_transforms(
12-
mut query: Query<
13-
(&Transform, &mut GlobalTransform),
14-
(Changed<Transform>, Without<Parent>, Without<Children>),
15-
>,
14+
mut query: ParamSet<(
15+
Query<
16+
(&Transform, &mut GlobalTransform),
17+
(Changed<Transform>, Without<Parent>, Without<Children>),
18+
>,
19+
Query<(Ref<Transform>, &mut GlobalTransform), Without<Children>>,
20+
)>,
21+
mut orphaned: RemovedComponents<Parent>,
1622
) {
1723
query
24+
.p0()
1825
.par_iter_mut()
1926
.for_each_mut(|(transform, mut global_transform)| {
2027
*global_transform = GlobalTransform::from(*transform);
2128
});
29+
// updated orphaned entities
30+
let mut query = query.p1();
31+
let mut iter = query.iter_many_mut(orphaned.iter());
32+
while let Some((transform, mut global_transform)) = iter.fetch_next() {
33+
if !transform.is_changed() {
34+
*global_transform = GlobalTransform::from(*transform);
35+
}
36+
}
2237
}
2338

2439
/// Update [`GlobalTransform`] component of entities based on entity hierarchy and
@@ -30,12 +45,15 @@ pub fn propagate_transforms(
3045
(Entity, &Children, Ref<Transform>, &mut GlobalTransform),
3146
Without<Parent>,
3247
>,
48+
mut orphaned: RemovedComponents<Parent>,
3349
transform_query: Query<(Ref<Transform>, &mut GlobalTransform, Option<&Children>), With<Parent>>,
3450
parent_query: Query<(Entity, Ref<Parent>)>,
3551
) {
52+
let mut orphaned = orphaned.iter().collect::<Vec<_>>();
53+
orphaned.sort_unstable();
3654
root_query.par_iter_mut().for_each_mut(
3755
|(entity, children, transform, mut global_transform)| {
38-
let changed = transform.is_changed();
56+
let changed = transform.is_changed() || orphaned.binary_search(&entity).is_ok();
3957
if changed {
4058
*global_transform = GlobalTransform::from(*transform);
4159
}
@@ -161,10 +179,62 @@ mod test {
161179
use bevy_tasks::{ComputeTaskPool, TaskPool};
162180

163181
use crate::components::{GlobalTransform, Transform};
164-
use crate::systems::*;
165-
use crate::TransformBundle;
182+
use crate::{propagate_transforms, sync_simple_transforms, TransformBundle};
166183
use bevy_hierarchy::{BuildChildren, BuildWorldChildren, Children, Parent};
167184

185+
#[test]
186+
fn correct_parent_removed() {
187+
ComputeTaskPool::init(TaskPool::default);
188+
let mut world = World::default();
189+
let offset_global_transform =
190+
|offset| GlobalTransform::from(Transform::from_xyz(offset, offset, offset));
191+
let offset_transform =
192+
|offset| TransformBundle::from_transform(Transform::from_xyz(offset, offset, offset));
193+
194+
let mut schedule = Schedule::new();
195+
schedule.add_system(sync_simple_transforms);
196+
schedule.add_system(propagate_transforms);
197+
198+
let mut command_queue = CommandQueue::default();
199+
let mut commands = Commands::new(&mut command_queue, &world);
200+
let root = commands.spawn(offset_transform(3.3)).id();
201+
let parent = commands.spawn(offset_transform(4.4)).id();
202+
let child = commands.spawn(offset_transform(5.5)).id();
203+
commands.entity(parent).set_parent(root);
204+
commands.entity(child).set_parent(parent);
205+
command_queue.apply(&mut world);
206+
schedule.run(&mut world);
207+
208+
assert_ne!(
209+
world.get::<GlobalTransform>(parent).unwrap(),
210+
&GlobalTransform::from(Transform::IDENTITY)
211+
);
212+
213+
// Remove parent of `parent`
214+
let mut command_queue = CommandQueue::default();
215+
let mut commands = Commands::new(&mut command_queue, &world);
216+
commands.entity(parent).remove_parent();
217+
command_queue.apply(&mut world);
218+
schedule.run(&mut world);
219+
220+
assert_eq!(
221+
world.get::<GlobalTransform>(parent).unwrap(),
222+
&offset_global_transform(4.4)
223+
);
224+
225+
// Remove parent of `child`
226+
let mut command_queue = CommandQueue::default();
227+
let mut commands = Commands::new(&mut command_queue, &world);
228+
commands.entity(child).remove_parent();
229+
command_queue.apply(&mut world);
230+
schedule.run(&mut world);
231+
232+
assert_eq!(
233+
world.get::<GlobalTransform>(child).unwrap(),
234+
&offset_global_transform(5.5)
235+
);
236+
}
237+
168238
#[test]
169239
fn did_propagate() {
170240
ComputeTaskPool::init(TaskPool::default);
@@ -375,8 +445,8 @@ mod test {
375445
let mut temp = World::new();
376446
let mut app = App::new();
377447

378-
app.add_system(propagate_transforms)
379-
.add_system(sync_simple_transforms);
448+
app.add_system(sync_simple_transforms);
449+
app.add_system(propagate_transforms);
380450

381451
fn setup_world(world: &mut World) -> (Entity, Entity) {
382452
let mut grandchild = Entity::from_raw(0);

0 commit comments

Comments
 (0)