bevy_transform/
helper.rs

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