
# Objective In #17905 we swapped to a named field on `ChildOf` to help resolve variable naming ambiguity of child vs parent (ex: `child_of.parent` clearly reads as "I am accessing the parent of the child_of relationship", whereas `child_of.0` is less clear). Unfortunately this has the side effect of making initialization less ideal. `ChildOf { parent }` reads just as well as `ChildOf(parent)`, but `ChildOf { parent: root }` doesn't read nearly as well as `ChildOf(root)`. ## Solution Move back to `ChildOf(pub Entity)` but add a `child_of.parent()` function and use it for all accesses. The downside here is that users are no longer "forced" to access the parent field with `parent` nomenclature, but I think this strikes the right balance. Take a look at the diff. I think the results provide strong evidence for this change. Initialization has the benefit of reading much better _and_ of taking up significantly less space, as many lines go from 3 to 1, and we're cutting out a bunch of syntax in some cases. Sadly I do think this should land in 0.16 as the cost of doing this _after_ the relationships migration is high.
147 lines
4.8 KiB
Rust
147 lines
4.8 KiB
Rust
//! System parameter for computing up-to-date [`GlobalTransform`]s.
|
|
|
|
use bevy_ecs::{
|
|
hierarchy::ChildOf,
|
|
prelude::Entity,
|
|
query::QueryEntityError,
|
|
system::{Query, SystemParam},
|
|
};
|
|
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 ChildOf>,
|
|
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::EntityDoesNotExist(error) => {
|
|
if ancestor {
|
|
MalformedHierarchy(error.entity)
|
|
} else {
|
|
NoSuchEntity(error.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 {
|
|
use alloc::{vec, vec::Vec};
|
|
use core::f32::consts::TAU;
|
|
|
|
use bevy_app::App;
|
|
use bevy_ecs::{hierarchy::ChildOf, system::SystemState};
|
|
use bevy_math::{Quat, Vec3};
|
|
|
|
use crate::{
|
|
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(transform);
|
|
|
|
if let Some(parent) = entity {
|
|
e.insert(ChildOf(parent));
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|