bevy_transform/
helper.rs

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