Add TransformBundle (#3054)

# Objective

- Bevy currently has no simple way to make an "empty" Entity work correctly in a Hierachy.
  - The current Solution is to insert a Tuple instead: 

```rs
.insert_bundle((Transform::default(), GlobalTransform::default()))
```

## Solution

* Add a `TransformBundle` that combines the Components:

```rs
.insert_bundle(TransformBundle::default())
```

* The code is based on #2331, except for missing the more controversial usage of `TransformBundle` as a Sub-bundle in preexisting Bundles.

Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
MinerSebas 2022-02-06 01:07:55 +00:00
parent 7604665880
commit 59ee512292
9 changed files with 113 additions and 96 deletions

View File

@ -26,7 +26,8 @@ use bevy_render::{
use bevy_scene::Scene; use bevy_scene::Scene;
use bevy_transform::{ use bevy_transform::{
hierarchy::{BuildWorldChildren, WorldChildBuilder}, hierarchy::{BuildWorldChildren, WorldChildBuilder},
prelude::{GlobalTransform, Transform}, prelude::Transform,
TransformBundle,
}; };
use bevy_utils::{HashMap, HashSet}; use bevy_utils::{HashMap, HashSet};
use gltf::{ use gltf::{
@ -289,7 +290,7 @@ async fn load_gltf<'a, 'b>(
let mut world = World::default(); let mut world = World::default();
world world
.spawn() .spawn()
.insert_bundle((Transform::identity(), GlobalTransform::identity())) .insert_bundle(TransformBundle::identity())
.with_children(|parent| { .with_children(|parent| {
for node in scene.nodes() { for node in scene.nodes() {
let result = load_node(&node, parent, load_context, &buffer_data); let result = load_node(&node, parent, load_context, &buffer_data);
@ -462,10 +463,9 @@ fn load_node(
) -> Result<(), GltfError> { ) -> Result<(), GltfError> {
let transform = gltf_node.transform(); let transform = gltf_node.transform();
let mut gltf_error = None; let mut gltf_error = None;
let mut node = world_builder.spawn_bundle(( let mut node = world_builder.spawn_bundle(TransformBundle::from(Transform::from_matrix(
Transform::from_matrix(Mat4::from_cols_array_2d(&transform.matrix())), Mat4::from_cols_array_2d(&transform.matrix()),
GlobalTransform::identity(), )));
));
if let Some(name) = gltf_node.name() { if let Some(name) = gltf_node.name() {
node.insert(Name::new(name.to_string())); node.insert(Name::new(name.to_string()));

View File

@ -7,8 +7,9 @@ use std::ops::Mul;
/// Describe the position of an entity relative to the reference frame. /// Describe the position of an entity relative to the reference frame.
/// ///
/// * To place or move an entity, you should set its [`Transform`]. /// * To place or move an entity, you should set its [`Transform`].
/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
/// * To get the global position of an entity, you should get its [`GlobalTransform`]. /// * To get the global position of an entity, you should get its [`GlobalTransform`].
/// * For transform hierarchies to work correctly, you must have both a [`Transform`] and a [`GlobalTransform`].
/// * You may use the [`TransformBundle`](crate::TransformBundle) to guarantee this.
/// ///
/// ## [`Transform`] and [`GlobalTransform`] /// ## [`Transform`] and [`GlobalTransform`]
/// ///
@ -20,16 +21,6 @@ use std::ops::Mul;
/// [`GlobalTransform`] is updated from [`Transform`] in the system /// [`GlobalTransform`] is updated from [`Transform`] in the system
/// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system). /// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system).
/// ///
/// In pseudo code:
/// ```ignore
/// for entity in entities_without_parent:
/// set entity.global_transform to entity.transform
/// recursively:
/// set parent to current entity
/// for child in parent.children:
/// set child.global_transform to parent.global_transform * child.transform
/// ```
///
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you /// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
/// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag /// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
/// before the [`GlobalTransform`] is updated. /// before the [`GlobalTransform`] is updated.

View File

@ -8,8 +8,9 @@ use std::ops::Mul;
/// to its parent position. /// to its parent position.
/// ///
/// * To place or move an entity, you should set its [`Transform`]. /// * To place or move an entity, you should set its [`Transform`].
/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
/// * To get the global position of an entity, you should get its [`GlobalTransform`]. /// * To get the global position of an entity, you should get its [`GlobalTransform`].
/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
/// * You may use the [`TransformBundle`](crate::TransformBundle) to guarantee this.
/// ///
/// ## [`Transform`] and [`GlobalTransform`] /// ## [`Transform`] and [`GlobalTransform`]
/// ///
@ -21,16 +22,6 @@ use std::ops::Mul;
/// [`GlobalTransform`] is updated from [`Transform`] in the system /// [`GlobalTransform`] is updated from [`Transform`] in the system
/// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system). /// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system).
/// ///
/// In pseudo code:
/// ```ignore
/// for entity in entities_without_parent:
/// set entity.global_transform to entity.transform
/// recursively:
/// set parent to current entity
/// for child in parent.children:
/// set child.global_transform to parent.global_transform * child.transform
/// ```
///
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you /// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
/// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag /// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
/// before the [`GlobalTransform`] is updated. /// before the [`GlobalTransform`] is updated.

View File

@ -11,13 +11,76 @@ pub mod transform_propagate_system;
#[doc(hidden)] #[doc(hidden)]
pub mod prelude { pub mod prelude {
#[doc(hidden)] #[doc(hidden)]
pub use crate::{components::*, hierarchy::*, TransformPlugin}; pub use crate::{components::*, hierarchy::*, TransformBundle, TransformPlugin};
} }
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel}; use bevy_ecs::{
bundle::Bundle,
schedule::{ParallelSystemDescriptorCoercion, SystemLabel},
};
use prelude::{parent_update_system, Children, GlobalTransform, Parent, PreviousParent, Transform}; use prelude::{parent_update_system, Children, GlobalTransform, Parent, PreviousParent, Transform};
/// A [`Bundle`] of the [`Transform`] and [`GlobalTransform`]
/// [`Component`](bevy_ecs::component::Component)s, which describe the position of an entity.
///
/// * To place or move an entity, you should set its [`Transform`].
/// * To get the global position of an entity, you should get its [`GlobalTransform`].
/// * For transform hierarchies to work correctly, you must have both a [`Transform`] and a [`GlobalTransform`].
/// * You may use the [`TransformBundle`] to guarantee this.
///
/// ## [`Transform`] and [`GlobalTransform`]
///
/// [`Transform`] is the position of an entity relative to its parent position, or the reference
/// frame if it doesn't have a [`Parent`](Parent).
///
/// [`GlobalTransform`] is the position of an entity relative to the reference frame.
///
/// [`GlobalTransform`] is updated from [`Transform`] in the system
/// [`transform_propagate_system`](crate::transform_propagate_system::transform_propagate_system).
///
/// This system runs in stage [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate). If you
/// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag
/// before the [`GlobalTransform`] is updated.
#[derive(Bundle, Clone, Copy, Debug, Default)]
pub struct TransformBundle {
/// The transform of the entity.
pub local: Transform,
/// The global transform of the entity.
pub global: GlobalTransform,
}
impl TransformBundle {
/// Creates a new [`TransformBundle`] from a [`Transform`].
///
/// This initializes [`GlobalTransform`] as identity, to be updated later by the
/// [`CoreStage::PostUpdate`](crate::CoreStage::PostUpdate) stage.
#[inline]
pub const fn from_transform(transform: Transform) -> Self {
TransformBundle {
local: transform,
// Note: `..Default::default()` cannot be used here, because it isn't const
..Self::identity()
}
}
/// Creates a new identity [`TransformBundle`], with no translation, rotation, and a scale of 1
/// on all axes.
#[inline]
pub const fn identity() -> Self {
TransformBundle {
local: Transform::identity(),
global: GlobalTransform::identity(),
}
}
}
impl From<Transform> for TransformBundle {
#[inline]
fn from(transform: Transform) -> Self {
Self::from_transform(transform)
}
}
/// The base plugin for handling [`Transform`] components /// The base plugin for handling [`Transform`] components
#[derive(Default)] #[derive(Default)]
pub struct TransformPlugin; pub struct TransformPlugin;

View File

@ -82,7 +82,10 @@ mod test {
}; };
use super::*; use super::*;
use crate::hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren}; use crate::{
hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren},
TransformBundle,
};
#[test] #[test]
fn did_propagate() { fn did_propagate() {
@ -96,33 +99,23 @@ mod test {
schedule.add_stage("update", update_stage); schedule.add_stage("update", update_stage);
// Root entity // Root entity
world.spawn().insert_bundle(( world
Transform::from_xyz(1.0, 0.0, 0.0), .spawn()
GlobalTransform::identity(), .insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)));
));
let mut children = Vec::new(); let mut children = Vec::new();
world world
.spawn() .spawn()
.insert_bundle(( .insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)))
Transform::from_xyz(1.0, 0.0, 0.0),
GlobalTransform::identity(),
))
.with_children(|parent| { .with_children(|parent| {
children.push( children.push(
parent parent
.spawn_bundle(( .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.)))
Transform::from_xyz(0.0, 2.0, 0.),
GlobalTransform::identity(),
))
.id(), .id(),
); );
children.push( children.push(
parent parent
.spawn_bundle(( .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.)))
Transform::from_xyz(0.0, 0.0, 3.),
GlobalTransform::identity(),
))
.id(), .id(),
); );
}); });
@ -155,25 +148,16 @@ mod test {
let mut commands = Commands::new(&mut queue, &world); let mut commands = Commands::new(&mut queue, &world);
let mut children = Vec::new(); let mut children = Vec::new();
commands commands
.spawn_bundle(( .spawn_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0)))
Transform::from_xyz(1.0, 0.0, 0.0),
GlobalTransform::identity(),
))
.with_children(|parent| { .with_children(|parent| {
children.push( children.push(
parent parent
.spawn_bundle(( .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.0)))
Transform::from_xyz(0.0, 2.0, 0.0),
GlobalTransform::identity(),
))
.id(), .id(),
); );
children.push( children.push(
parent parent
.spawn_bundle(( .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.0)))
Transform::from_xyz(0.0, 0.0, 3.0),
GlobalTransform::identity(),
))
.id(), .id(),
); );
}); });

View File

@ -54,7 +54,7 @@ fn setup(
}); });
// light // light
commands.spawn_bundle(PointLightBundle { commands.spawn_bundle(PointLightBundle {
transform: Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)), transform: Transform::from_xyz(50.0, 50.0, 50.0),
point_light: PointLight { point_light: PointLight {
intensity: 600000., intensity: 600000.,
range: 100., range: 100.,
@ -64,8 +64,7 @@ fn setup(
}); });
// camera // camera
commands.spawn_bundle(OrthographicCameraBundle { commands.spawn_bundle(OrthographicCameraBundle {
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 8.0)) transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y),
.looking_at(Vec3::default(), Vec3::Y),
orthographic_projection: OrthographicProjection { orthographic_projection: OrthographicProjection {
scale: 0.01, scale: 0.01,
..Default::default() ..Default::default()

View File

@ -38,10 +38,7 @@ fn setup(
// Spawn the scene as a child of another entity. This first scene will be translated backward // Spawn the scene as a child of another entity. This first scene will be translated backward
// with its parent // with its parent
commands commands
.spawn_bundle(( .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, -1.0)))
Transform::from_xyz(0.0, 0.0, -1.0),
GlobalTransform::identity(),
))
.with_children(|parent| { .with_children(|parent| {
parent.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")); parent.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"));
}); });

View File

@ -60,7 +60,7 @@ fn spawn_tasks(mut commands: Commands, thread_pool: Res<AsyncComputeTaskPool>) {
} }
// Such hard work, all done! // Such hard work, all done!
Transform::from_translation(Vec3::new(x as f32, y as f32, z as f32)) Transform::from_xyz(x as f32, y as f32, z as f32)
}); });
// Spawn new entity and add our new task as a component // Spawn new entity and add our new task as a component
@ -107,13 +107,13 @@ fn setup_env(mut commands: Commands) {
// lights // lights
commands.spawn_bundle(PointLightBundle { commands.spawn_bundle(PointLightBundle {
transform: Transform::from_translation(Vec3::new(4.0, 12.0, 15.0)), transform: Transform::from_xyz(4.0, 12.0, 15.0),
..Default::default() ..Default::default()
}); });
// camera // camera
commands.spawn_bundle(PerspectiveCameraBundle { commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_translation(Vec3::new(offset, offset, 15.0)) transform: Transform::from_xyz(offset, offset, 15.0)
.looking_at(Vec3::new(offset, offset, 0.0), Vec3::Y), .looking_at(Vec3::new(offset, offset, 0.0), Vec3::Y),
..Default::default() ..Default::default()
}); });

View File

@ -117,10 +117,11 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
.map(|i| { .map(|i| {
let height = rand::thread_rng().gen_range(-0.1..0.1); let height = rand::thread_rng().gen_range(-0.1..0.1);
commands commands
.spawn_bundle(( .spawn_bundle(TransformBundle::from(Transform::from_xyz(
Transform::from_xyz(i as f32, height - 0.2, j as f32), i as f32,
GlobalTransform::identity(), height - 0.2,
)) j as f32,
)))
.with_children(|cell| { .with_children(|cell| {
cell.spawn_scene(cell_scene.clone()); cell.spawn_scene(cell_scene.clone());
}); });
@ -133,8 +134,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
// spawn the game character // spawn the game character
game.player.entity = Some( game.player.entity = Some(
commands commands
.spawn_bundle(( .spawn_bundle(TransformBundle::from(Transform {
Transform {
translation: Vec3::new( translation: Vec3::new(
game.player.i as f32, game.player.i as f32,
game.board[game.player.j][game.player.i].height, game.board[game.player.j][game.player.i].height,
@ -142,9 +142,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
), ),
rotation: Quat::from_rotation_y(-std::f32::consts::FRAC_PI_2), rotation: Quat::from_rotation_y(-std::f32::consts::FRAC_PI_2),
..Default::default() ..Default::default()
}, }))
GlobalTransform::identity(),
))
.with_children(|cell| { .with_children(|cell| {
cell.spawn_scene(asset_server.load("models/AlienCake/alien.glb#Scene0")); cell.spawn_scene(asset_server.load("models/AlienCake/alien.glb#Scene0"));
}) })
@ -324,17 +322,11 @@ fn spawn_bonus(
} }
game.bonus.entity = Some( game.bonus.entity = Some(
commands commands
.spawn_bundle(( .spawn_bundle(TransformBundle::from(Transform::from_xyz(
Transform {
translation: Vec3::new(
game.bonus.i as f32, game.bonus.i as f32,
game.board[game.bonus.j][game.bonus.i].height + 0.2, game.board[game.bonus.j][game.bonus.i].height + 0.2,
game.bonus.j as f32, game.bonus.j as f32,
), )))
..Default::default()
},
GlobalTransform::identity(),
))
.with_children(|children| { .with_children(|children| {
children.spawn_bundle(PointLightBundle { children.spawn_bundle(PointLightBundle {
point_light: PointLight { point_light: PointLight {