From 219e75b22f3aaf473faeee4382e36a7fcdf67dff Mon Sep 17 00:00:00 2001 From: devil-ira Date: Fri, 12 May 2023 13:18:05 +0200 Subject: [PATCH 1/5] Add `TransformHelper` --- crates/bevy_transform/Cargo.toml | 2 + .../src/components/transform.rs | 9 ++ crates/bevy_transform/src/helper.rs | 137 ++++++++++++++++++ crates/bevy_transform/src/lib.rs | 5 +- 4 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 crates/bevy_transform/src/helper.rs diff --git a/crates/bevy_transform/Cargo.toml b/crates/bevy_transform/Cargo.toml index 0b426b6bf9e57..0a1e23054a105 100644 --- a/crates/bevy_transform/Cargo.toml +++ b/crates/bevy_transform/Cargo.toml @@ -19,6 +19,8 @@ serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] bevy_tasks = { path = "../bevy_tasks", version = "0.11.0-dev" } +approx = "0.5.1" +glam = { version = "0.23", features = ["approx"] } [features] serialize = ["dep:serde", "bevy_math/serialize"] diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index 0e8b76076d418..5c231f8bdc6d3 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -420,6 +420,15 @@ impl Mul for Transform { } } +impl Mul for Transform { + type Output = GlobalTransform; + + #[inline] + fn mul(self, global_transform: GlobalTransform) -> Self::Output { + GlobalTransform::from(self) * global_transform + } +} + impl Mul for Transform { type Output = Vec3; diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs new file mode 100644 index 0000000000000..9f1789988ee90 --- /dev/null +++ b/crates/bevy_transform/src/helper.rs @@ -0,0 +1,137 @@ +//! System parameter for computing up-to-date [`GlobalTransform`]s. + +use bevy_ecs::{ + prelude::Entity, + query::QueryEntityError, + system::{Query, SystemParam}, +}; +use bevy_hierarchy::{HierarchyQueryExt, Parent}; + +use crate::components::{GlobalTransform, Transform}; + +/// System parameter for computing up-to-date [`GlobalTransform`]s. +#[derive(SystemParam)] +pub struct TransformHelper<'w, 's> { + parent_query: Query<'w, 's, &'static Parent>, + transform_query: Query<'w, 's, &'static Transform>, +} + +impl<'w, 's> TransformHelper<'w, 's> { + /// Computes the [`GlobalTransform`] of the given entity from the [`Transform`] component on it and its ancestors. + pub fn compute_global_transform( + &self, + entity: Entity, + ) -> Result { + let transform = self + .transform_query + .get(entity) + .map_err(|err| map_error(err, false))?; + + let mut global_transform = GlobalTransform::from(*transform); + + for entity in self.parent_query.iter_ancestors(entity) { + let transform = self + .transform_query + .get(entity) + .map_err(|err| map_error(err, true))?; + + global_transform = *transform * global_transform; + } + + Ok(global_transform) + } +} + +fn map_error(err: QueryEntityError, ancestor: bool) -> ComputeGlobalTransformError { + use ComputeGlobalTransformError::*; + match err { + QueryEntityError::QueryDoesNotMatch(entity) => MissingTransform(entity), + QueryEntityError::NoSuchEntity(entity) => { + if ancestor { + MalformedHierarchy(entity) + } else { + NoSuchEntity(entity) + } + } + QueryEntityError::AliasedMutability(_) => unreachable!(), + } +} + +/// Error returned by [`TransformHelper::compute_global_transform`]. +#[derive(Debug)] +pub enum ComputeGlobalTransformError { + /// The entity or one of its ancestors is missing the [`Transform`] component. + MissingTransform(Entity), + /// The entity does not exist. + NoSuchEntity(Entity), + /// An ancestor is missing. + /// This probably means that your hierarchy has been improperly maintained. + MalformedHierarchy(Entity), +} + +#[cfg(test)] +mod tests { + use std::f32::consts::TAU; + + use bevy_app::App; + use bevy_ecs::system::SystemState; + use bevy_hierarchy::BuildWorldChildren; + use bevy_math::{Quat, Vec3}; + + use crate::{ + components::{GlobalTransform, Transform}, + helper::TransformHelper, + TransformBundle, TransformPlugin, + }; + + #[test] + fn test() { + // Single transform + test_inner(vec![Transform::from_translation(Vec3::X) + .with_rotation(Quat::from_rotation_y(TAU / 4.)) + .with_scale(Vec3::splat(2.))]); + + // Transform hierarchy + test_inner(vec![ + Transform::from_translation(Vec3::X) + .with_rotation(Quat::from_rotation_y(TAU / 4.)) + .with_scale(Vec3::splat(2.)), + Transform::from_translation(Vec3::Y) + .with_rotation(Quat::from_rotation_z(TAU / 3.)) + .with_scale(Vec3::splat(1.5)), + Transform::from_translation(Vec3::Z) + .with_rotation(Quat::from_rotation_x(TAU / 2.)) + .with_scale(Vec3::splat(0.3)), + ]); + } + + fn test_inner(transforms: Vec) { + let mut app = App::new(); + app.add_plugin(TransformPlugin); + + let mut entity = None; + + for transform in transforms { + let mut e = app.world.spawn(TransformBundle::from(transform)); + + if let Some(entity) = entity { + e.set_parent(entity); + } + + entity = Some(e.id()); + } + + let leaf_entity = entity.unwrap(); + + app.update(); + + let transform = *app.world.get::(leaf_entity).unwrap(); + + let mut state = SystemState::::new(&mut app.world); + let helper = state.get(&app.world); + + let computed_transform = helper.compute_global_transform(leaf_entity).unwrap(); + + approx::assert_abs_diff_eq!(transform.affine(), computed_transform.affine()); + } +} diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index c8b01c1098e94..2c0512e2d3cf6 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -6,6 +6,7 @@ pub mod commands; /// The basic components of the transform crate pub mod components; +pub mod helper; /// Systems responsible for transform propagation pub mod systems; @@ -13,8 +14,8 @@ pub mod systems; pub mod prelude { #[doc(hidden)] pub use crate::{ - commands::BuildChildrenTransformExt, components::*, TransformBundle, TransformPlugin, - TransformPoint, + commands::BuildChildrenTransformExt, components::*, helper::TransformHelper, + TransformBundle, TransformPlugin, TransformPoint, }; } From e0f0a281c9cf64cdfdc39b5e21ff0aad3ea5f5de Mon Sep 17 00:00:00 2001 From: devil-ira Date: Fri, 12 May 2023 13:47:03 +0200 Subject: [PATCH 2/5] Update test name --- crates/bevy_transform/src/helper.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index 9f1789988ee90..1b06e0987391e 100644 --- a/crates/bevy_transform/src/helper.rs +++ b/crates/bevy_transform/src/helper.rs @@ -85,14 +85,14 @@ mod tests { }; #[test] - fn test() { + fn match_transform_propagation_systems() { // Single transform - test_inner(vec![Transform::from_translation(Vec3::X) + match_transform_propagation_systems_inner(vec![Transform::from_translation(Vec3::X) .with_rotation(Quat::from_rotation_y(TAU / 4.)) .with_scale(Vec3::splat(2.))]); // Transform hierarchy - test_inner(vec![ + match_transform_propagation_systems_inner(vec![ Transform::from_translation(Vec3::X) .with_rotation(Quat::from_rotation_y(TAU / 4.)) .with_scale(Vec3::splat(2.)), @@ -105,7 +105,7 @@ mod tests { ]); } - fn test_inner(transforms: Vec) { + fn match_transform_propagation_systems_inner(transforms: Vec) { let mut app = App::new(); app.add_plugin(TransformPlugin); From 436932fc0ab9b90c5ab5b8ccfd43c06a89861c3d Mon Sep 17 00:00:00 2001 From: devil-ira Date: Fri, 2 Jun 2023 14:10:43 +0200 Subject: [PATCH 3/5] doc --- crates/bevy_transform/src/helper.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index 1b06e0987391e..67084305d9bc5 100644 --- a/crates/bevy_transform/src/helper.rs +++ b/crates/bevy_transform/src/helper.rs @@ -10,6 +10,11 @@ use bevy_hierarchy::{HierarchyQueryExt, Parent}; use crate::components::{GlobalTransform, Transform}; /// System parameter for computing up-to-date [`GlobalTransform`]s. +/// +/// Computing an entity's [`GlobalTransform`] can be expensive so it is recommended +/// you use the [`GlobalTransform`] component stored on the entity, unless you need +/// a [`GlobalTransform`] that reflects the changes made to any [`Transform`]s since +/// the last time the transform propagation systems ran. #[derive(SystemParam)] pub struct TransformHelper<'w, 's> { parent_query: Query<'w, 's, &'static Parent>, From 0bb6fe638a128d29ad24639c0beb6cc1d96c5487 Mon Sep 17 00:00:00 2001 From: devil-ira Date: Fri, 2 Jun 2023 14:18:50 +0200 Subject: [PATCH 4/5] Update glam --- crates/bevy_transform/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_transform/Cargo.toml b/crates/bevy_transform/Cargo.toml index 0a1e23054a105..caf91acabeff1 100644 --- a/crates/bevy_transform/Cargo.toml +++ b/crates/bevy_transform/Cargo.toml @@ -20,7 +20,7 @@ serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] bevy_tasks = { path = "../bevy_tasks", version = "0.11.0-dev" } approx = "0.5.1" -glam = { version = "0.23", features = ["approx"] } +glam = { version = "0.24", features = ["approx"] } [features] serialize = ["dep:serde", "bevy_math/serialize"] From 20db756ccad7b03ae452d8e199817e81dd217fe8 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 18 Oct 2023 15:46:59 -0400 Subject: [PATCH 5/5] Fix breaking change Co-authored-by: Noah --- crates/bevy_transform/src/helper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index 67084305d9bc5..67a92db7328b3 100644 --- a/crates/bevy_transform/src/helper.rs +++ b/crates/bevy_transform/src/helper.rs @@ -112,7 +112,7 @@ mod tests { fn match_transform_propagation_systems_inner(transforms: Vec) { let mut app = App::new(); - app.add_plugin(TransformPlugin); + app.add_plugins(TransformPlugin); let mut entity = None;