Skip to content

Commit

Permalink
Add system parameter for computing up-to-date GlobalTransforms (#8603)
Browse files Browse the repository at this point in the history
# 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>
  • Loading branch information
3 people authored Oct 18, 2023
1 parent 0dc7e60 commit 4b65a53
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 2 deletions.
2 changes: 2 additions & 0 deletions crates/bevy_transform/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
9 changes: 9 additions & 0 deletions crates/bevy_transform/src/components/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
142 changes: 142 additions & 0 deletions crates/bevy_transform/src/helper.rs
Original file line number Diff line number Diff line change
@@ -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());
}
}
5 changes: 3 additions & 2 deletions crates/bevy_transform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
pub mod commands;
/// The basic components of the transform crate
pub mod components;
pub mod helper;
/// Systems responsible for transform propagation
pub mod systems;

#[doc(hidden)]
pub mod prelude {
#[doc(hidden)]
pub use crate::{
commands::BuildChildrenTransformExt, components::*, TransformBundle, TransformPlugin,
TransformPoint,
commands::BuildChildrenTransformExt, components::*, helper::TransformHelper,
TransformBundle, TransformPlugin, TransformPoint,
};
}

Expand Down

0 comments on commit 4b65a53

Please sign in to comment.