Skip to content

Commit 4b65a53

Browse files
tim-blackbirdalice-i-cecileNoahShomette
authored
Add system parameter for computing up-to-date GlobalTransforms (#8603)
# Objective Add a way to easily compute the up-to-date `GlobalTransform` of an entity. ## Solution Add the `TransformHelper`(Name pending) system parameter with the `compute_global_transform` method that takes an `Entity` and returns a `GlobalTransform` if successful. ## Changelog - Added the `TransformHelper` system parameter for computing the up-to-date `GlobalTransform` of an entity. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Noah <noahshomette@gmail.com>
1 parent 0dc7e60 commit 4b65a53

File tree

4 files changed

+156
-2
lines changed

4 files changed

+156
-2
lines changed

crates/bevy_transform/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ serde = { version = "1", features = ["derive"], optional = true }
1919

2020
[dev-dependencies]
2121
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0-dev" }
22+
approx = "0.5.1"
23+
glam = { version = "0.24", features = ["approx"] }
2224

2325
[features]
2426
serialize = ["dep:serde", "bevy_math/serialize"]

crates/bevy_transform/src/components/transform.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,15 @@ impl Mul<Transform> for Transform {
420420
}
421421
}
422422

423+
impl Mul<GlobalTransform> for Transform {
424+
type Output = GlobalTransform;
425+
426+
#[inline]
427+
fn mul(self, global_transform: GlobalTransform) -> Self::Output {
428+
GlobalTransform::from(self) * global_transform
429+
}
430+
}
431+
423432
impl Mul<Vec3> for Transform {
424433
type Output = Vec3;
425434

crates/bevy_transform/src/helper.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//! System parameter for computing up-to-date [`GlobalTransform`]s.
2+
3+
use bevy_ecs::{
4+
prelude::Entity,
5+
query::QueryEntityError,
6+
system::{Query, SystemParam},
7+
};
8+
use bevy_hierarchy::{HierarchyQueryExt, Parent};
9+
10+
use crate::components::{GlobalTransform, Transform};
11+
12+
/// System parameter for computing up-to-date [`GlobalTransform`]s.
13+
///
14+
/// Computing an entity's [`GlobalTransform`] can be expensive so it is recommended
15+
/// you use the [`GlobalTransform`] component stored on the entity, unless you need
16+
/// a [`GlobalTransform`] that reflects the changes made to any [`Transform`]s since
17+
/// the last time the transform propagation systems ran.
18+
#[derive(SystemParam)]
19+
pub struct TransformHelper<'w, 's> {
20+
parent_query: Query<'w, 's, &'static Parent>,
21+
transform_query: Query<'w, 's, &'static Transform>,
22+
}
23+
24+
impl<'w, 's> TransformHelper<'w, 's> {
25+
/// Computes the [`GlobalTransform`] of the given entity from the [`Transform`] component on it and its ancestors.
26+
pub fn compute_global_transform(
27+
&self,
28+
entity: Entity,
29+
) -> Result<GlobalTransform, ComputeGlobalTransformError> {
30+
let transform = self
31+
.transform_query
32+
.get(entity)
33+
.map_err(|err| map_error(err, false))?;
34+
35+
let mut global_transform = GlobalTransform::from(*transform);
36+
37+
for entity in self.parent_query.iter_ancestors(entity) {
38+
let transform = self
39+
.transform_query
40+
.get(entity)
41+
.map_err(|err| map_error(err, true))?;
42+
43+
global_transform = *transform * global_transform;
44+
}
45+
46+
Ok(global_transform)
47+
}
48+
}
49+
50+
fn map_error(err: QueryEntityError, ancestor: bool) -> ComputeGlobalTransformError {
51+
use ComputeGlobalTransformError::*;
52+
match err {
53+
QueryEntityError::QueryDoesNotMatch(entity) => MissingTransform(entity),
54+
QueryEntityError::NoSuchEntity(entity) => {
55+
if ancestor {
56+
MalformedHierarchy(entity)
57+
} else {
58+
NoSuchEntity(entity)
59+
}
60+
}
61+
QueryEntityError::AliasedMutability(_) => unreachable!(),
62+
}
63+
}
64+
65+
/// Error returned by [`TransformHelper::compute_global_transform`].
66+
#[derive(Debug)]
67+
pub enum ComputeGlobalTransformError {
68+
/// The entity or one of its ancestors is missing the [`Transform`] component.
69+
MissingTransform(Entity),
70+
/// The entity does not exist.
71+
NoSuchEntity(Entity),
72+
/// An ancestor is missing.
73+
/// This probably means that your hierarchy has been improperly maintained.
74+
MalformedHierarchy(Entity),
75+
}
76+
77+
#[cfg(test)]
78+
mod tests {
79+
use std::f32::consts::TAU;
80+
81+
use bevy_app::App;
82+
use bevy_ecs::system::SystemState;
83+
use bevy_hierarchy::BuildWorldChildren;
84+
use bevy_math::{Quat, Vec3};
85+
86+
use crate::{
87+
components::{GlobalTransform, Transform},
88+
helper::TransformHelper,
89+
TransformBundle, TransformPlugin,
90+
};
91+
92+
#[test]
93+
fn match_transform_propagation_systems() {
94+
// Single transform
95+
match_transform_propagation_systems_inner(vec![Transform::from_translation(Vec3::X)
96+
.with_rotation(Quat::from_rotation_y(TAU / 4.))
97+
.with_scale(Vec3::splat(2.))]);
98+
99+
// Transform hierarchy
100+
match_transform_propagation_systems_inner(vec![
101+
Transform::from_translation(Vec3::X)
102+
.with_rotation(Quat::from_rotation_y(TAU / 4.))
103+
.with_scale(Vec3::splat(2.)),
104+
Transform::from_translation(Vec3::Y)
105+
.with_rotation(Quat::from_rotation_z(TAU / 3.))
106+
.with_scale(Vec3::splat(1.5)),
107+
Transform::from_translation(Vec3::Z)
108+
.with_rotation(Quat::from_rotation_x(TAU / 2.))
109+
.with_scale(Vec3::splat(0.3)),
110+
]);
111+
}
112+
113+
fn match_transform_propagation_systems_inner(transforms: Vec<Transform>) {
114+
let mut app = App::new();
115+
app.add_plugins(TransformPlugin);
116+
117+
let mut entity = None;
118+
119+
for transform in transforms {
120+
let mut e = app.world.spawn(TransformBundle::from(transform));
121+
122+
if let Some(entity) = entity {
123+
e.set_parent(entity);
124+
}
125+
126+
entity = Some(e.id());
127+
}
128+
129+
let leaf_entity = entity.unwrap();
130+
131+
app.update();
132+
133+
let transform = *app.world.get::<GlobalTransform>(leaf_entity).unwrap();
134+
135+
let mut state = SystemState::<TransformHelper>::new(&mut app.world);
136+
let helper = state.get(&app.world);
137+
138+
let computed_transform = helper.compute_global_transform(leaf_entity).unwrap();
139+
140+
approx::assert_abs_diff_eq!(transform.affine(), computed_transform.affine());
141+
}
142+
}

crates/bevy_transform/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66
pub mod commands;
77
/// The basic components of the transform crate
88
pub mod components;
9+
pub mod helper;
910
/// Systems responsible for transform propagation
1011
pub mod systems;
1112

1213
#[doc(hidden)]
1314
pub mod prelude {
1415
#[doc(hidden)]
1516
pub use crate::{
16-
commands::BuildChildrenTransformExt, components::*, TransformBundle, TransformPlugin,
17-
TransformPoint,
17+
commands::BuildChildrenTransformExt, components::*, helper::TransformHelper,
18+
TransformBundle, TransformPlugin, TransformPoint,
1819
};
1920
}
2021

0 commit comments

Comments
 (0)