Add system parameter for computing up-to-date GlobalTransform
s (#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>
This commit is contained in:
parent
0dc7e60d0e
commit
4b65a533f1
@ -19,6 +19,8 @@ serde = { version = "1", features = ["derive"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.12.0-dev" }
|
||||
approx = "0.5.1"
|
||||
glam = { version = "0.24", features = ["approx"] }
|
||||
|
||||
[features]
|
||||
serialize = ["dep:serde", "bevy_math/serialize"]
|
||||
|
@ -420,6 +420,15 @@ impl Mul<Transform> for Transform {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<GlobalTransform> for Transform {
|
||||
type Output = GlobalTransform;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, global_transform: GlobalTransform) -> Self::Output {
|
||||
GlobalTransform::from(self) * global_transform
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vec3> for Transform {
|
||||
type Output = Vec3;
|
||||
|
||||
|
142
crates/bevy_transform/src/helper.rs
Normal file
142
crates/bevy_transform/src/helper.rs
Normal file
@ -0,0 +1,142 @@
|
||||
//! System parameter for computing up-to-date [`GlobalTransform`]s.
|
||||
|
||||
use bevy_ecs::{
|
||||
prelude::Entity,
|
||||
query::QueryEntityError,
|
||||
system::{Query, SystemParam},
|
||||
};
|
||||
use bevy_hierarchy::{HierarchyQueryExt, Parent};
|
||||
|
||||
use crate::components::{GlobalTransform, Transform};
|
||||
|
||||
/// System parameter for computing up-to-date [`GlobalTransform`]s.
|
||||
///
|
||||
/// Computing an entity's [`GlobalTransform`] can be expensive so it is recommended
|
||||
/// you use the [`GlobalTransform`] component stored on the entity, unless you need
|
||||
/// a [`GlobalTransform`] that reflects the changes made to any [`Transform`]s since
|
||||
/// the last time the transform propagation systems ran.
|
||||
#[derive(SystemParam)]
|
||||
pub struct TransformHelper<'w, 's> {
|
||||
parent_query: Query<'w, 's, &'static Parent>,
|
||||
transform_query: Query<'w, 's, &'static Transform>,
|
||||
}
|
||||
|
||||
impl<'w, 's> TransformHelper<'w, 's> {
|
||||
/// Computes the [`GlobalTransform`] of the given entity from the [`Transform`] component on it and its ancestors.
|
||||
pub fn compute_global_transform(
|
||||
&self,
|
||||
entity: Entity,
|
||||
) -> Result<GlobalTransform, ComputeGlobalTransformError> {
|
||||
let transform = self
|
||||
.transform_query
|
||||
.get(entity)
|
||||
.map_err(|err| map_error(err, false))?;
|
||||
|
||||
let mut global_transform = GlobalTransform::from(*transform);
|
||||
|
||||
for entity in self.parent_query.iter_ancestors(entity) {
|
||||
let transform = self
|
||||
.transform_query
|
||||
.get(entity)
|
||||
.map_err(|err| map_error(err, true))?;
|
||||
|
||||
global_transform = *transform * global_transform;
|
||||
}
|
||||
|
||||
Ok(global_transform)
|
||||
}
|
||||
}
|
||||
|
||||
fn map_error(err: QueryEntityError, ancestor: bool) -> ComputeGlobalTransformError {
|
||||
use ComputeGlobalTransformError::*;
|
||||
match err {
|
||||
QueryEntityError::QueryDoesNotMatch(entity) => MissingTransform(entity),
|
||||
QueryEntityError::NoSuchEntity(entity) => {
|
||||
if ancestor {
|
||||
MalformedHierarchy(entity)
|
||||
} else {
|
||||
NoSuchEntity(entity)
|
||||
}
|
||||
}
|
||||
QueryEntityError::AliasedMutability(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned by [`TransformHelper::compute_global_transform`].
|
||||
#[derive(Debug)]
|
||||
pub enum ComputeGlobalTransformError {
|
||||
/// The entity or one of its ancestors is missing the [`Transform`] component.
|
||||
MissingTransform(Entity),
|
||||
/// The entity does not exist.
|
||||
NoSuchEntity(Entity),
|
||||
/// An ancestor is missing.
|
||||
/// This probably means that your hierarchy has been improperly maintained.
|
||||
MalformedHierarchy(Entity),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::f32::consts::TAU;
|
||||
|
||||
use bevy_app::App;
|
||||
use bevy_ecs::system::SystemState;
|
||||
use bevy_hierarchy::BuildWorldChildren;
|
||||
use bevy_math::{Quat, Vec3};
|
||||
|
||||
use crate::{
|
||||
components::{GlobalTransform, Transform},
|
||||
helper::TransformHelper,
|
||||
TransformBundle, TransformPlugin,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn match_transform_propagation_systems() {
|
||||
// Single transform
|
||||
match_transform_propagation_systems_inner(vec![Transform::from_translation(Vec3::X)
|
||||
.with_rotation(Quat::from_rotation_y(TAU / 4.))
|
||||
.with_scale(Vec3::splat(2.))]);
|
||||
|
||||
// Transform hierarchy
|
||||
match_transform_propagation_systems_inner(vec![
|
||||
Transform::from_translation(Vec3::X)
|
||||
.with_rotation(Quat::from_rotation_y(TAU / 4.))
|
||||
.with_scale(Vec3::splat(2.)),
|
||||
Transform::from_translation(Vec3::Y)
|
||||
.with_rotation(Quat::from_rotation_z(TAU / 3.))
|
||||
.with_scale(Vec3::splat(1.5)),
|
||||
Transform::from_translation(Vec3::Z)
|
||||
.with_rotation(Quat::from_rotation_x(TAU / 2.))
|
||||
.with_scale(Vec3::splat(0.3)),
|
||||
]);
|
||||
}
|
||||
|
||||
fn match_transform_propagation_systems_inner(transforms: Vec<Transform>) {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(TransformPlugin);
|
||||
|
||||
let mut entity = None;
|
||||
|
||||
for transform in transforms {
|
||||
let mut e = app.world.spawn(TransformBundle::from(transform));
|
||||
|
||||
if let Some(entity) = entity {
|
||||
e.set_parent(entity);
|
||||
}
|
||||
|
||||
entity = Some(e.id());
|
||||
}
|
||||
|
||||
let leaf_entity = entity.unwrap();
|
||||
|
||||
app.update();
|
||||
|
||||
let transform = *app.world.get::<GlobalTransform>(leaf_entity).unwrap();
|
||||
|
||||
let mut state = SystemState::<TransformHelper>::new(&mut app.world);
|
||||
let helper = state.get(&app.world);
|
||||
|
||||
let computed_transform = helper.compute_global_transform(leaf_entity).unwrap();
|
||||
|
||||
approx::assert_abs_diff_eq!(transform.affine(), computed_transform.affine());
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
pub mod commands;
|
||||
/// The basic components of the transform crate
|
||||
pub mod components;
|
||||
pub mod helper;
|
||||
/// Systems responsible for transform propagation
|
||||
pub mod systems;
|
||||
|
||||
@ -13,8 +14,8 @@ pub mod systems;
|
||||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{
|
||||
commands::BuildChildrenTransformExt, components::*, TransformBundle, TransformPlugin,
|
||||
TransformPoint,
|
||||
commands::BuildChildrenTransformExt, components::*, helper::TransformHelper,
|
||||
TransformBundle, TransformPlugin, TransformPoint,
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user