Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add system parameter for computing up-to-date GlobalTransforms #8603

Merged
merged 7 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
tim-blackbird marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably mention that this takes read access on Parent and Transform

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use a const generic for ancestor?

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)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how important but would be nice to derive Error on all error things :)

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
Loading