1use crate::components::{GlobalTransform, Transform};
2use bevy_ecs::{
3 change_detection::Ref,
4 prelude::{Changed, DetectChanges, Entity, Query, With, Without},
5 query::{Added, Or},
6 removal_detection::RemovedComponents,
7 system::{Local, ParamSet},
8};
9use bevy_hierarchy::{Children, Parent};
10
11pub fn sync_simple_transforms(
15 mut query: ParamSet<(
16 Query<
17 (&Transform, &mut GlobalTransform),
18 (
19 Or<(Changed<Transform>, Added<GlobalTransform>)>,
20 Without<Parent>,
21 Without<Children>,
22 ),
23 >,
24 Query<(Ref<Transform>, &mut GlobalTransform), (Without<Parent>, Without<Children>)>,
25 )>,
26 mut orphaned: RemovedComponents<Parent>,
27) {
28 query
30 .p0()
31 .par_iter_mut()
32 .for_each(|(transform, mut global_transform)| {
33 *global_transform = GlobalTransform::from(*transform);
34 });
35 let mut query = query.p1();
37 let mut iter = query.iter_many_mut(orphaned.read());
38 while let Some((transform, mut global_transform)) = iter.fetch_next() {
39 if !transform.is_changed() && !global_transform.is_added() {
40 *global_transform = GlobalTransform::from(*transform);
41 }
42 }
43}
44
45pub fn propagate_transforms(
50 mut root_query: Query<
51 (Entity, &Children, Ref<Transform>, &mut GlobalTransform),
52 Without<Parent>,
53 >,
54 mut orphaned: RemovedComponents<Parent>,
55 transform_query: Query<(Ref<Transform>, &mut GlobalTransform, Option<&Children>), With<Parent>>,
56 parent_query: Query<(Entity, Ref<Parent>), With<GlobalTransform>>,
57 mut orphaned_entities: Local<Vec<Entity>>,
58) {
59 orphaned_entities.clear();
60 orphaned_entities.extend(orphaned.read());
61 orphaned_entities.sort_unstable();
62 root_query.par_iter_mut().for_each(
63 |(entity, children, transform, mut global_transform)| {
64 let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
65 if changed {
66 *global_transform = GlobalTransform::from(*transform);
67 }
68
69 for (child, actual_parent) in parent_query.iter_many(children) {
70 assert_eq!(
71 actual_parent.get(), entity,
72 "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
73 );
74 #[expect(unsafe_code, reason = "`propagate_recursive()` is unsafe due to its use of `Query::get_unchecked()`.")]
83 unsafe {
84 propagate_recursive(
85 &global_transform,
86 &transform_query,
87 &parent_query,
88 child,
89 changed || actual_parent.is_changed(),
90 );
91 }
92 }
93 },
94 );
95}
96
97#[expect(
111 unsafe_code,
112 reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met."
113)]
114unsafe fn propagate_recursive(
115 parent: &GlobalTransform,
116 transform_query: &Query<
117 (Ref<Transform>, &mut GlobalTransform, Option<&Children>),
118 With<Parent>,
119 >,
120 parent_query: &Query<(Entity, Ref<Parent>), With<GlobalTransform>>,
121 entity: Entity,
122 mut changed: bool,
123) {
124 let (global_matrix, children) = {
125 let Ok((transform, mut global_transform, children)) =
126 (unsafe { transform_query.get_unchecked(entity) }) else {
153 return;
154 };
155
156 changed |= transform.is_changed() || global_transform.is_added();
157 if changed {
158 *global_transform = parent.mul_transform(*transform);
159 }
160 (global_transform, children)
161 };
162
163 let Some(children) = children else { return };
164 for (child, actual_parent) in parent_query.iter_many(children) {
165 assert_eq!(
166 actual_parent.get(), entity,
167 "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
168 );
169 unsafe {
175 propagate_recursive(
176 global_matrix.as_ref(),
177 transform_query,
178 parent_query,
179 child,
180 changed || actual_parent.is_changed(),
181 );
182 }
183 }
184}
185
186#[cfg(test)]
187mod test {
188 use bevy_app::prelude::*;
189 use bevy_ecs::{prelude::*, world::CommandQueue};
190 use bevy_math::{vec3, Vec3};
191 use bevy_tasks::{ComputeTaskPool, TaskPool};
192
193 use crate::systems::*;
194 use bevy_hierarchy::{BuildChildren, ChildBuild};
195
196 #[test]
197 fn correct_parent_removed() {
198 ComputeTaskPool::get_or_init(TaskPool::default);
199 let mut world = World::default();
200 let offset_global_transform =
201 |offset| GlobalTransform::from(Transform::from_xyz(offset, offset, offset));
202 let offset_transform = |offset| Transform::from_xyz(offset, offset, offset);
203
204 let mut schedule = Schedule::default();
205 schedule.add_systems((sync_simple_transforms, propagate_transforms));
206
207 let mut command_queue = CommandQueue::default();
208 let mut commands = Commands::new(&mut command_queue, &world);
209 let root = commands.spawn(offset_transform(3.3)).id();
210 let parent = commands.spawn(offset_transform(4.4)).id();
211 let child = commands.spawn(offset_transform(5.5)).id();
212 commands.entity(parent).set_parent(root);
213 commands.entity(child).set_parent(parent);
214 command_queue.apply(&mut world);
215 schedule.run(&mut world);
216
217 assert_eq!(
218 world.get::<GlobalTransform>(parent).unwrap(),
219 &offset_global_transform(4.4 + 3.3),
220 "The transform systems didn't run, ie: `GlobalTransform` wasn't updated",
221 );
222
223 let mut command_queue = CommandQueue::default();
225 let mut commands = Commands::new(&mut command_queue, &world);
226 commands.entity(parent).remove_parent();
227 command_queue.apply(&mut world);
228 schedule.run(&mut world);
229
230 assert_eq!(
231 world.get::<GlobalTransform>(parent).unwrap(),
232 &offset_global_transform(4.4),
233 "The global transform of an orphaned entity wasn't updated properly",
234 );
235
236 let mut command_queue = CommandQueue::default();
238 let mut commands = Commands::new(&mut command_queue, &world);
239 commands.entity(child).remove_parent();
240 command_queue.apply(&mut world);
241 schedule.run(&mut world);
242
243 assert_eq!(
244 world.get::<GlobalTransform>(child).unwrap(),
245 &offset_global_transform(5.5),
246 "The global transform of an orphaned entity wasn't updated properly",
247 );
248 }
249
250 #[test]
251 fn did_propagate() {
252 ComputeTaskPool::get_or_init(TaskPool::default);
253 let mut world = World::default();
254
255 let mut schedule = Schedule::default();
256 schedule.add_systems((sync_simple_transforms, propagate_transforms));
257
258 world.spawn(Transform::from_xyz(1.0, 0.0, 0.0));
260
261 let mut children = Vec::new();
262 world
263 .spawn(Transform::from_xyz(1.0, 0.0, 0.0))
264 .with_children(|parent| {
265 children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.)).id());
266 children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.)).id());
267 });
268 schedule.run(&mut world);
269
270 assert_eq!(
271 *world.get::<GlobalTransform>(children[0]).unwrap(),
272 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
273 );
274
275 assert_eq!(
276 *world.get::<GlobalTransform>(children[1]).unwrap(),
277 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
278 );
279 }
280
281 #[test]
282 fn did_propagate_command_buffer() {
283 let mut world = World::default();
284
285 let mut schedule = Schedule::default();
286 schedule.add_systems((sync_simple_transforms, propagate_transforms));
287
288 let mut queue = CommandQueue::default();
290 let mut commands = Commands::new(&mut queue, &world);
291 let mut children = Vec::new();
292 commands
293 .spawn(Transform::from_xyz(1.0, 0.0, 0.0))
294 .with_children(|parent| {
295 children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
296 children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.0)).id());
297 });
298 queue.apply(&mut world);
299 schedule.run(&mut world);
300
301 assert_eq!(
302 *world.get::<GlobalTransform>(children[0]).unwrap(),
303 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
304 );
305
306 assert_eq!(
307 *world.get::<GlobalTransform>(children[1]).unwrap(),
308 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
309 );
310 }
311
312 #[test]
313 fn correct_children() {
314 ComputeTaskPool::get_or_init(TaskPool::default);
315 let mut world = World::default();
316
317 let mut schedule = Schedule::default();
318 schedule.add_systems((sync_simple_transforms, propagate_transforms));
319
320 let mut children = Vec::new();
322 let parent = {
323 let mut command_queue = CommandQueue::default();
324 let mut commands = Commands::new(&mut command_queue, &world);
325 let parent = commands.spawn(Transform::from_xyz(1.0, 0.0, 0.0)).id();
326 commands.entity(parent).with_children(|parent| {
327 children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
328 children.push(parent.spawn(Transform::from_xyz(0.0, 3.0, 0.0)).id());
329 });
330 command_queue.apply(&mut world);
331 schedule.run(&mut world);
332 parent
333 };
334
335 assert_eq!(
336 world
337 .get::<Children>(parent)
338 .unwrap()
339 .iter()
340 .cloned()
341 .collect::<Vec<_>>(),
342 children,
343 );
344
345 {
347 let mut command_queue = CommandQueue::default();
348 let mut commands = Commands::new(&mut command_queue, &world);
349 commands.entity(children[1]).add_child(children[0]);
350 command_queue.apply(&mut world);
351 schedule.run(&mut world);
352 }
353
354 assert_eq!(
355 world
356 .get::<Children>(parent)
357 .unwrap()
358 .iter()
359 .cloned()
360 .collect::<Vec<_>>(),
361 vec![children[1]]
362 );
363
364 assert_eq!(
365 world
366 .get::<Children>(children[1])
367 .unwrap()
368 .iter()
369 .cloned()
370 .collect::<Vec<_>>(),
371 vec![children[0]]
372 );
373
374 assert!(world.despawn(children[0]));
375
376 schedule.run(&mut world);
377
378 assert_eq!(
379 world
380 .get::<Children>(parent)
381 .unwrap()
382 .iter()
383 .cloned()
384 .collect::<Vec<_>>(),
385 vec![children[1]]
386 );
387 }
388
389 #[test]
390 fn correct_transforms_when_no_children() {
391 let mut app = App::new();
392 ComputeTaskPool::get_or_init(TaskPool::default);
393
394 app.add_systems(Update, (sync_simple_transforms, propagate_transforms));
395
396 let translation = vec3(1.0, 0.0, 0.0);
397
398 let mut child = Entity::from_raw(0);
400 let mut grandchild = Entity::from_raw(1);
401 let parent = app
402 .world_mut()
403 .spawn(Transform::from_translation(translation))
404 .with_children(|builder| {
405 child = builder
406 .spawn(Transform::IDENTITY)
407 .with_children(|builder| {
408 grandchild = builder.spawn(Transform::IDENTITY).id();
409 })
410 .id();
411 })
412 .id();
413
414 app.update();
415
416 assert_eq!(&**app.world().get::<Children>(parent).unwrap(), &[child]);
418 assert_eq!(
419 &**app.world().get::<Children>(child).unwrap(),
420 &[grandchild]
421 );
422 app.update();
424
425 let mut state = app.world_mut().query::<&GlobalTransform>();
426 for global in state.iter(app.world()) {
427 assert_eq!(global, &GlobalTransform::from_translation(translation));
428 }
429 }
430
431 #[test]
432 #[should_panic]
433 fn panic_when_hierarchy_cycle() {
434 ComputeTaskPool::get_or_init(TaskPool::default);
435 let mut temp = World::new();
438 let mut app = App::new();
439
440 app.add_systems(Update, (propagate_transforms, sync_simple_transforms));
441
442 fn setup_world(world: &mut World) -> (Entity, Entity) {
443 let mut grandchild = Entity::from_raw(0);
444 let child = world
445 .spawn(Transform::IDENTITY)
446 .with_children(|builder| {
447 grandchild = builder.spawn(Transform::IDENTITY).id();
448 })
449 .id();
450 (child, grandchild)
451 }
452
453 let (temp_child, temp_grandchild) = setup_world(&mut temp);
454 let (child, grandchild) = setup_world(app.world_mut());
455
456 assert_eq!(temp_child, child);
457 assert_eq!(temp_grandchild, grandchild);
458
459 app.world_mut()
460 .spawn(Transform::IDENTITY)
461 .add_children(&[child]);
462 core::mem::swap(
463 &mut *app.world_mut().get_mut::<Parent>(child).unwrap(),
464 &mut *temp.get_mut::<Parent>(grandchild).unwrap(),
465 );
466
467 app.update();
468 }
469
470 #[test]
471 fn global_transform_should_not_be_overwritten_after_reparenting() {
472 let translation = Vec3::ONE;
473 let mut world = World::new();
474
475 let mut schedule = Schedule::default();
477 schedule.add_systems((sync_simple_transforms, propagate_transforms));
478
479 let mut spawn_transform_bundle =
481 || world.spawn(Transform::from_translation(translation)).id();
482
483 let parent = spawn_transform_bundle();
485 let child = spawn_transform_bundle();
486 world.entity_mut(parent).add_child(child);
487
488 schedule.run(&mut world);
490
491 let parent_global_transform = *world.entity(parent).get::<GlobalTransform>().unwrap();
493 let child_global_transform = *world.entity(child).get::<GlobalTransform>().unwrap();
494 assert!(parent_global_transform
495 .translation()
496 .abs_diff_eq(translation, 0.1));
497 assert!(child_global_transform
498 .translation()
499 .abs_diff_eq(2. * translation, 0.1));
500
501 world.entity_mut(child).remove_parent();
503 world.entity_mut(parent).add_child(child);
504
505 schedule.run(&mut world);
507
508 assert_eq!(
510 parent_global_transform,
511 *world.entity(parent).get::<GlobalTransform>().unwrap()
512 );
513 assert_eq!(
514 child_global_transform,
515 *world.entity(child).get::<GlobalTransform>().unwrap()
516 );
517 }
518}