diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index db59b3a8f25ba..a91c1beaf90af 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -26,7 +26,8 @@ use bevy_render::{ use bevy_scene::Scene; use bevy_transform::{ hierarchy::{BuildWorldChildren, WorldChildBuilder}, - prelude::{GlobalTransform, Transform}, + prelude::Transform, + TransformBundle, }; use bevy_utils::{HashMap, HashSet}; use gltf::{ @@ -289,7 +290,7 @@ async fn load_gltf<'a, 'b>( let mut world = World::default(); world .spawn() - .insert_bundle((Transform::identity(), GlobalTransform::identity())) + .insert_bundle(TransformBundle::identity()) .with_children(|parent| { for node in scene.nodes() { let result = load_node(&node, parent, load_context, &buffer_data); @@ -462,10 +463,9 @@ fn load_node( ) -> Result<(), GltfError> { let transform = gltf_node.transform(); let mut gltf_error = None; - let mut node = world_builder.spawn_bundle(( - Transform::from_matrix(Mat4::from_cols_array_2d(&transform.matrix())), - GlobalTransform::identity(), - )); + let mut node = world_builder.spawn_bundle(TransformBundle::from(Transform::from_matrix( + Mat4::from_cols_array_2d(&transform.matrix()), + ))); if let Some(name) = gltf_node.name() { node.insert(Name::new(name.to_string())); diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index dd163cc2f5570..f66afde173d6d 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -7,8 +7,9 @@ use std::ops::Mul; /// Describe the position of an entity relative to the reference frame. /// /// * 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`]. +/// * 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`] /// @@ -20,16 +21,6 @@ use std::ops::Mul; /// [`GlobalTransform`] is updated from [`Transform`] in the 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 /// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag /// before the [`GlobalTransform`] is updated. diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index c9b5ec52a1cfa..80787df3194dc 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -8,8 +8,9 @@ use std::ops::Mul; /// to its parent position. /// /// * 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 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`] /// @@ -21,16 +22,6 @@ use std::ops::Mul; /// [`GlobalTransform`] is updated from [`Transform`] in the 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 /// update the[`Transform`] of an entity in this stage or after, you will notice a 1 frame lag /// before the [`GlobalTransform`] is updated. diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index d9ddf29507078..45ab1f2ab7988 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -11,13 +11,76 @@ pub mod transform_propagate_system; #[doc(hidden)] pub mod prelude { #[doc(hidden)] - pub use crate::{components::*, hierarchy::*, TransformPlugin}; + pub use crate::{components::*, hierarchy::*, TransformBundle, TransformPlugin}; } 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}; +/// 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 for TransformBundle { + #[inline] + fn from(transform: Transform) -> Self { + Self::from_transform(transform) + } +} /// The base plugin for handling [`Transform`] components #[derive(Default)] pub struct TransformPlugin; diff --git a/crates/bevy_transform/src/transform_propagate_system.rs b/crates/bevy_transform/src/transform_propagate_system.rs index a4ab7aadcdf89..fb6ef6b1db7f8 100644 --- a/crates/bevy_transform/src/transform_propagate_system.rs +++ b/crates/bevy_transform/src/transform_propagate_system.rs @@ -82,7 +82,10 @@ mod test { }; use super::*; - use crate::hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren}; + use crate::{ + hierarchy::{parent_update_system, BuildChildren, BuildWorldChildren}, + TransformBundle, + }; #[test] fn did_propagate() { @@ -96,33 +99,23 @@ mod test { schedule.add_stage("update", update_stage); // Root entity - world.spawn().insert_bundle(( - Transform::from_xyz(1.0, 0.0, 0.0), - GlobalTransform::identity(), - )); + world + .spawn() + .insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))); let mut children = Vec::new(); world .spawn() - .insert_bundle(( - Transform::from_xyz(1.0, 0.0, 0.0), - GlobalTransform::identity(), - )) + .insert_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))) .with_children(|parent| { children.push( parent - .spawn_bundle(( - Transform::from_xyz(0.0, 2.0, 0.), - GlobalTransform::identity(), - )) + .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.))) .id(), ); children.push( parent - .spawn_bundle(( - Transform::from_xyz(0.0, 0.0, 3.), - GlobalTransform::identity(), - )) + .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.))) .id(), ); }); @@ -155,25 +148,16 @@ mod test { let mut commands = Commands::new(&mut queue, &world); let mut children = Vec::new(); commands - .spawn_bundle(( - Transform::from_xyz(1.0, 0.0, 0.0), - GlobalTransform::identity(), - )) + .spawn_bundle(TransformBundle::from(Transform::from_xyz(1.0, 0.0, 0.0))) .with_children(|parent| { children.push( parent - .spawn_bundle(( - Transform::from_xyz(0.0, 2.0, 0.0), - GlobalTransform::identity(), - )) + .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 2.0, 0.0))) .id(), ); children.push( parent - .spawn_bundle(( - Transform::from_xyz(0.0, 0.0, 3.0), - GlobalTransform::identity(), - )) + .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, 3.0))) .id(), ); }); diff --git a/examples/3d/pbr.rs b/examples/3d/pbr.rs index bd2081fbd85cb..09d51029d99c3 100644 --- a/examples/3d/pbr.rs +++ b/examples/3d/pbr.rs @@ -54,7 +54,7 @@ fn setup( }); // light 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 { intensity: 600000., range: 100., @@ -64,8 +64,7 @@ fn setup( }); // camera commands.spawn_bundle(OrthographicCameraBundle { - transform: Transform::from_translation(Vec3::new(0.0, 0.0, 8.0)) - .looking_at(Vec3::default(), Vec3::Y), + transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y), orthographic_projection: OrthographicProjection { scale: 0.01, ..Default::default() diff --git a/examples/3d/update_gltf_scene.rs b/examples/3d/update_gltf_scene.rs index 2bb945a05c3b1..db06d79fd3fe5 100644 --- a/examples/3d/update_gltf_scene.rs +++ b/examples/3d/update_gltf_scene.rs @@ -38,10 +38,7 @@ fn setup( // Spawn the scene as a child of another entity. This first scene will be translated backward // with its parent commands - .spawn_bundle(( - Transform::from_xyz(0.0, 0.0, -1.0), - GlobalTransform::identity(), - )) + .spawn_bundle(TransformBundle::from(Transform::from_xyz(0.0, 0.0, -1.0))) .with_children(|parent| { parent.spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0")); }); diff --git a/examples/async_tasks/async_compute.rs b/examples/async_tasks/async_compute.rs index c5c0c519e2c04..164da6d558430 100644 --- a/examples/async_tasks/async_compute.rs +++ b/examples/async_tasks/async_compute.rs @@ -60,7 +60,7 @@ fn spawn_tasks(mut commands: Commands, thread_pool: Res) { } // 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 @@ -107,13 +107,13 @@ fn setup_env(mut commands: Commands) { // lights 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() }); // camera 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), ..Default::default() }); diff --git a/examples/game/alien_cake_addict.rs b/examples/game/alien_cake_addict.rs index 7f23e6f9dd8be..683d085c5f3f1 100644 --- a/examples/game/alien_cake_addict.rs +++ b/examples/game/alien_cake_addict.rs @@ -117,10 +117,11 @@ fn setup(mut commands: Commands, asset_server: Res, mut game: ResMu .map(|i| { let height = rand::thread_rng().gen_range(-0.1..0.1); commands - .spawn_bundle(( - Transform::from_xyz(i as f32, height - 0.2, j as f32), - GlobalTransform::identity(), - )) + .spawn_bundle(TransformBundle::from(Transform::from_xyz( + i as f32, + height - 0.2, + j as f32, + ))) .with_children(|cell| { cell.spawn_scene(cell_scene.clone()); }); @@ -133,18 +134,15 @@ fn setup(mut commands: Commands, asset_server: Res, mut game: ResMu // spawn the game character game.player.entity = Some( commands - .spawn_bundle(( - Transform { - translation: Vec3::new( - game.player.i as f32, - game.board[game.player.j][game.player.i].height, - game.player.j as f32, - ), - rotation: Quat::from_rotation_y(-std::f32::consts::FRAC_PI_2), - ..Default::default() - }, - GlobalTransform::identity(), - )) + .spawn_bundle(TransformBundle::from(Transform { + translation: Vec3::new( + game.player.i as f32, + game.board[game.player.j][game.player.i].height, + game.player.j as f32, + ), + rotation: Quat::from_rotation_y(-std::f32::consts::FRAC_PI_2), + ..Default::default() + })) .with_children(|cell| { cell.spawn_scene(asset_server.load("models/AlienCake/alien.glb#Scene0")); }) @@ -324,17 +322,11 @@ fn spawn_bonus( } game.bonus.entity = Some( commands - .spawn_bundle(( - Transform { - translation: Vec3::new( - game.bonus.i as f32, - game.board[game.bonus.j][game.bonus.i].height + 0.2, - game.bonus.j as f32, - ), - ..Default::default() - }, - GlobalTransform::identity(), - )) + .spawn_bundle(TransformBundle::from(Transform::from_xyz( + game.bonus.i as f32, + game.board[game.bonus.j][game.bonus.i].height + 0.2, + game.bonus.j as f32, + ))) .with_children(|children| { children.spawn_bundle(PointLightBundle { point_light: PointLight {