bevy/crates/bevy_transform/src/helper.rs

148 lines
4.9 KiB
Rust
Raw Normal View History

//! 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 thiserror::Error;
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, Error)]
pub enum ComputeGlobalTransformError {
/// The entity or one of its ancestors is missing the [`Transform`] component.
#[error("The entity {0:?} or one of its ancestors is missing the `Transform` component")]
MissingTransform(Entity),
/// The entity does not exist.
#[error("The entity {0:?} does not exist")]
NoSuchEntity(Entity),
/// An ancestor is missing.
/// This probably means that your hierarchy has been improperly maintained.
#[error("The ancestor {0:?} is missing")]
MalformedHierarchy(Entity),
}
#[cfg(test)]
mod tests {
Add `core` and `alloc` over `std` Lints (#15281) # Objective - Fixes #6370 - Closes #6581 ## Solution - Added the following lints to the workspace: - `std_instead_of_core` - `std_instead_of_alloc` - `alloc_instead_of_core` - Used `cargo +nightly fmt` with [item level use formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Item%5C%3A) to split all `use` statements into single items. - Used `cargo clippy --workspace --all-targets --all-features --fix --allow-dirty` to _attempt_ to resolve the new linting issues, and intervened where the lint was unable to resolve the issue automatically (usually due to needing an `extern crate alloc;` statement in a crate root). - Manually removed certain uses of `std` where negative feature gating prevented `--all-features` from finding the offending uses. - Used `cargo +nightly fmt` with [crate level use formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Crate%5C%3A) to re-merge all `use` statements matching Bevy's previous styling. - Manually fixed cases where the `fmt` tool could not re-merge `use` statements due to conditional compilation attributes. ## Testing - Ran CI locally ## Migration Guide The MSRV is now 1.81. Please update to this version or higher. ## Notes - This is a _massive_ change to try and push through, which is why I've outlined the semi-automatic steps I used to create this PR, in case this fails and someone else tries again in the future. - Making this change has no impact on user code, but does mean Bevy contributors will be warned to use `core` and `alloc` instead of `std` where possible. - This lint is a critical first step towards investigating `no_std` options for Bevy. --------- Co-authored-by: François Mockers <francois.mockers@vleue.com>
2024-09-27 00:59:59 +00:00
use core::f32::consts::TAU;
use bevy_app::App;
use bevy_ecs::system::SystemState;
use bevy_hierarchy::BuildChildren;
use bevy_math::{Quat, Vec3};
use crate::{
bundles::TransformBundle,
components::{GlobalTransform, Transform},
helper::TransformHelper,
plugins::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_mut().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(app.world_mut());
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());
}
}