1use 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#[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 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#[derive(Debug, Error, Display)]
68pub enum ComputeGlobalTransformError {
69 #[display("The entity {_0:?} or one of its ancestors is missing the `Transform` component")]
71 #[error(ignore)]
72 MissingTransform(Entity),
73 #[display("The entity {_0:?} does not exist")]
75 #[error(ignore)]
76 NoSuchEntity(Entity),
77 #[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 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 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}