bevy/crates/bevy_transform/src/systems.rs
Dusty DeWeese 9f8bdeeeb9 Use Affine3A for GlobalTransform to allow any affine transformation (#4379)
# Objective

- Add capability to use `Affine3A`s for some `GlobalTransform`s. This allows affine transformations that are not possible using a single `Transform` such as shear and non-uniform scaling along an arbitrary axis.
- Related to #1755 and #2026

## Solution

- `GlobalTransform` becomes an enum wrapping either a `Transform` or an `Affine3A`.
- The API of `GlobalTransform` is minimized to avoid inefficiency, and to make it clear that operations should be performed using the underlying data types.
- using `GlobalTransform::Affine3A` disables transform propagation, because the main use is for cases that `Transform`s cannot support.

---

## Changelog

- `GlobalTransform`s can optionally support any affine transformation using an `Affine3A`.


Co-authored-by: Carter Anderson <mcanders1@gmail.com>
2022-07-16 00:51:12 +00:00

369 lines
12 KiB
Rust

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<Children>)>,
&Transform,
Changed<Transform>,
&mut GlobalTransform,
Entity,
),
Without<Parent>,
>,
mut transform_query: Query<(
&Transform,
Changed<Transform>,
&mut GlobalTransform,
&Parent,
)>,
children_query: Query<(&Children, Changed<Children>), (With<Parent>, With<GlobalTransform>)>,
) {
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<Transform>,
&mut GlobalTransform,
&Parent,
)>,
children_query: &Query<(&Children, Changed<Children>), (With<Parent>, With<GlobalTransform>)>,
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::<GlobalTransform>(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::<GlobalTransform>(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::<GlobalTransform>(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::<GlobalTransform>(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::<Children>(parent)
.unwrap()
.iter()
.cloned()
.collect::<Vec<_>>(),
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::<Children>(parent)
.unwrap()
.iter()
.cloned()
.collect::<Vec<_>>(),
vec![children[1]]
);
assert_eq!(
world
.get::<Children>(children[1])
.unwrap()
.iter()
.cloned()
.collect::<Vec<_>>(),
vec![children[0]]
);
assert!(world.despawn(children[0]));
schedule.run(&mut world);
assert_eq!(
world
.get::<Children>(parent)
.unwrap()
.iter()
.cloned()
.collect::<Vec<_>>(),
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::default())
.with_children(|builder| {
child = builder
.spawn_bundle((Transform::identity(), GlobalTransform::default()))
.with_children(|builder| {
grandchild = builder
.spawn_bundle((Transform::identity(), GlobalTransform::default()))
.id();
})
.id();
})
.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::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((Transform::identity(), GlobalTransform::default()))
.with_children(|builder| {
grandchild = builder
.spawn()
.insert_bundle((Transform::identity(), GlobalTransform::default()))
.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((Transform::default(), GlobalTransform::default()))
.push_children(&[child]);
std::mem::swap(
&mut *app.world.get_mut::<Parent>(child).unwrap(),
&mut *temp.get_mut::<Parent>(grandchild).unwrap(),
);
app.update();
}
}