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