Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

80
vendor/bevy_transform/src/commands.rs vendored Normal file
View File

@@ -0,0 +1,80 @@
//! Extension to [`EntityCommands`] to modify [`bevy_ecs::hierarchy`] hierarchies.
//! while preserving [`GlobalTransform`].
use crate::prelude::{GlobalTransform, Transform};
use bevy_ecs::{entity::Entity, hierarchy::ChildOf, system::EntityCommands, world::EntityWorldMut};
/// Collection of methods similar to the built-in parenting methods on [`EntityWorldMut`] and [`EntityCommands`], but preserving each
/// entity's [`GlobalTransform`].
pub trait BuildChildrenTransformExt {
/// Change this entity's parent while preserving this entity's [`GlobalTransform`]
/// by updating its [`Transform`].
///
/// Insert the [`ChildOf`] component directly if you don't want to also update the [`Transform`].
///
/// Note that both the hierarchy and transform updates will only execute
/// the next time commands are applied
/// (during [`ApplyDeferred`](bevy_ecs::schedule::ApplyDeferred)).
fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self;
/// Make this entity parentless while preserving this entity's [`GlobalTransform`]
/// by updating its [`Transform`] to be equal to its current [`GlobalTransform`].
///
/// See [`EntityWorldMut::remove_parent`] or [`EntityCommands::remove_parent`] for a method that doesn't update the [`Transform`].
///
/// Note that both the hierarchy and transform updates will only execute
/// the next time commands are applied
/// (during [`ApplyDeferred`](bevy_ecs::schedule::ApplyDeferred)).
fn remove_parent_in_place(&mut self) -> &mut Self;
}
impl BuildChildrenTransformExt for EntityCommands<'_> {
fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self {
self.queue(move |mut entity: EntityWorldMut| {
entity.set_parent_in_place(parent);
})
}
fn remove_parent_in_place(&mut self) -> &mut Self {
self.queue(move |mut entity: EntityWorldMut| {
entity.remove_parent_in_place();
})
}
}
impl BuildChildrenTransformExt for EntityWorldMut<'_> {
fn set_parent_in_place(&mut self, parent: Entity) -> &mut Self {
let child = self.id();
self.world_scope(|world| {
world.entity_mut(parent).add_child(child);
// FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436.
let mut update_transform = || {
let parent = *world.get_entity(parent).ok()?.get::<GlobalTransform>()?;
let child_global = *world.get_entity(child).ok()?.get::<GlobalTransform>()?;
let mut child_entity = world.get_entity_mut(child).ok()?;
let mut child = child_entity.get_mut::<Transform>()?;
*child = child_global.reparented_to(&parent);
Some(())
};
update_transform();
});
self
}
fn remove_parent_in_place(&mut self) -> &mut Self {
let child = self.id();
self.world_scope(|world| {
world.entity_mut(child).remove::<ChildOf>();
// FIXME: Replace this closure with a `try` block. See: https://github.com/rust-lang/rust/issues/31436.
let mut update_transform = || {
let child_global = *world.get_entity(child).ok()?.get::<GlobalTransform>()?;
let mut child_entity = world.get_entity_mut(child).ok()?;
let mut child = child_entity.get_mut::<Transform>()?;
*child = child_global.compute_transform();
Some(())
};
update_transform();
});
self
}
}

View File

@@ -0,0 +1,417 @@
use core::ops::Mul;
use super::Transform;
use bevy_math::{ops, Affine3A, Dir3, Isometry3d, Mat4, Quat, Vec3, Vec3A};
use derive_more::derive::From;
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
#[cfg(feature = "bevy-support")]
use bevy_ecs::{component::Component, hierarchy::validate_parent_has_component};
#[cfg(feature = "bevy_reflect")]
use {
bevy_ecs::reflect::ReflectComponent,
bevy_reflect::{std_traits::ReflectDefault, Reflect},
};
/// [`GlobalTransform`] is an affine transformation from entity-local coordinates to worldspace coordinates.
///
/// You cannot directly mutate [`GlobalTransform`]; instead, you change an entity's transform by manipulating
/// its [`Transform`], which indirectly causes Bevy to update its [`GlobalTransform`].
///
/// * To get the global transform of an entity, you should get its [`GlobalTransform`].
/// * For transform hierarchies to work correctly, you must have both a [`Transform`] and a [`GlobalTransform`].
/// [`GlobalTransform`] is automatically inserted whenever [`Transform`] is inserted.
///
/// ## [`Transform`] and [`GlobalTransform`]
///
/// [`Transform`] transforms an entity relative to its parent's reference frame, or relative to world space coordinates,
/// if it doesn't have a [`ChildOf`](bevy_ecs::hierarchy::ChildOf) component.
///
/// [`GlobalTransform`] is managed by Bevy; it is computed by successively applying the [`Transform`] of each ancestor
/// entity which has a Transform. This is done automatically by Bevy-internal systems in the system set
/// [`TransformPropagate`](crate::TransformSystem::TransformPropagate).
///
/// This system runs during [`PostUpdate`](bevy_app::PostUpdate). If you
/// update the [`Transform`] of an entity in this schedule or after, you will notice a 1 frame lag
/// before the [`GlobalTransform`] is updated.
///
/// # Examples
///
/// - [`transform`][transform_example]
///
/// [transform_example]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/transform.rs
#[derive(Debug, PartialEq, Clone, Copy, From)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy-support",
derive(Component),
component(on_insert = validate_parent_has_component::<GlobalTransform>)
)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Default, PartialEq, Debug, Clone)
)]
#[cfg_attr(
all(feature = "bevy_reflect", feature = "serialize"),
reflect(Serialize, Deserialize)
)]
pub struct GlobalTransform(Affine3A);
macro_rules! impl_local_axis {
($pos_name: ident, $neg_name: ident, $axis: ident) => {
#[doc=core::concat!("Return the local ", core::stringify!($pos_name), " vector (", core::stringify!($axis) ,").")]
#[inline]
pub fn $pos_name(&self) -> Dir3 {
Dir3::new_unchecked((self.0.matrix3 * Vec3::$axis).normalize())
}
#[doc=core::concat!("Return the local ", core::stringify!($neg_name), " vector (-", core::stringify!($axis) ,").")]
#[inline]
pub fn $neg_name(&self) -> Dir3 {
-self.$pos_name()
}
};
}
impl GlobalTransform {
/// An identity [`GlobalTransform`] that maps all points in space to themselves.
pub const IDENTITY: Self = Self(Affine3A::IDENTITY);
#[doc(hidden)]
#[inline]
pub fn from_xyz(x: f32, y: f32, z: f32) -> Self {
Self::from_translation(Vec3::new(x, y, z))
}
#[doc(hidden)]
#[inline]
pub fn from_translation(translation: Vec3) -> Self {
GlobalTransform(Affine3A::from_translation(translation))
}
#[doc(hidden)]
#[inline]
pub fn from_rotation(rotation: Quat) -> Self {
GlobalTransform(Affine3A::from_rotation_translation(rotation, Vec3::ZERO))
}
#[doc(hidden)]
#[inline]
pub fn from_scale(scale: Vec3) -> Self {
GlobalTransform(Affine3A::from_scale(scale))
}
#[doc(hidden)]
#[inline]
pub fn from_isometry(iso: Isometry3d) -> Self {
Self(iso.into())
}
/// Returns the 3d affine transformation matrix as a [`Mat4`].
#[inline]
pub fn compute_matrix(&self) -> Mat4 {
Mat4::from(self.0)
}
/// Returns the 3d affine transformation matrix as an [`Affine3A`].
#[inline]
pub fn affine(&self) -> Affine3A {
self.0
}
/// Returns the transformation as a [`Transform`].
///
/// The transform is expected to be non-degenerate and without shearing, or the output
/// will be invalid.
#[inline]
pub fn compute_transform(&self) -> Transform {
let (scale, rotation, translation) = self.0.to_scale_rotation_translation();
Transform {
translation,
rotation,
scale,
}
}
/// Returns the isometric part of the transformation as an [isometry]. Any scaling done by the
/// transformation will be ignored.
///
/// The transform is expected to be non-degenerate and without shearing, or the output
/// will be invalid.
///
/// [isometry]: Isometry3d
#[inline]
pub fn to_isometry(&self) -> Isometry3d {
let (_, rotation, translation) = self.0.to_scale_rotation_translation();
Isometry3d::new(translation, rotation)
}
/// Returns the [`Transform`] `self` would have if it was a child of an entity
/// with the `parent` [`GlobalTransform`].
///
/// This is useful if you want to "reparent" an [`Entity`](bevy_ecs::entity::Entity).
/// Say you have an entity `e1` that you want to turn into a child of `e2`,
/// but you want `e1` to keep the same global transform, even after re-parenting. You would use:
///
/// ```
/// # use bevy_transform::prelude::{GlobalTransform, Transform};
/// # use bevy_ecs::prelude::{Entity, Query, Component, Commands};
/// #[derive(Component)]
/// struct ToReparent {
/// new_parent: Entity,
/// }
/// fn reparent_system(
/// mut commands: Commands,
/// mut targets: Query<(&mut Transform, Entity, &GlobalTransform, &ToReparent)>,
/// transforms: Query<&GlobalTransform>,
/// ) {
/// for (mut transform, entity, initial, to_reparent) in targets.iter_mut() {
/// if let Ok(parent_transform) = transforms.get(to_reparent.new_parent) {
/// *transform = initial.reparented_to(parent_transform);
/// commands.entity(entity)
/// .remove::<ToReparent>()
/// .set_parent(to_reparent.new_parent);
/// }
/// }
/// }
/// ```
///
/// The transform is expected to be non-degenerate and without shearing, or the output
/// will be invalid.
#[inline]
pub fn reparented_to(&self, parent: &GlobalTransform) -> Transform {
let relative_affine = parent.affine().inverse() * self.affine();
let (scale, rotation, translation) = relative_affine.to_scale_rotation_translation();
Transform {
translation,
rotation,
scale,
}
}
/// Extracts `scale`, `rotation` and `translation` from `self`.
///
/// The transform is expected to be non-degenerate and without shearing, or the output
/// will be invalid.
#[inline]
pub fn to_scale_rotation_translation(&self) -> (Vec3, Quat, Vec3) {
self.0.to_scale_rotation_translation()
}
impl_local_axis!(right, left, X);
impl_local_axis!(up, down, Y);
impl_local_axis!(back, forward, Z);
/// Get the translation as a [`Vec3`].
#[inline]
pub fn translation(&self) -> Vec3 {
self.0.translation.into()
}
/// Get the translation as a [`Vec3A`].
#[inline]
pub fn translation_vec3a(&self) -> Vec3A {
self.0.translation
}
/// Get the rotation as a [`Quat`].
///
/// The transform is expected to be non-degenerate and without shearing, or the output will be invalid.
///
/// # Warning
///
/// This is calculated using `to_scale_rotation_translation`, meaning that you
/// should probably use it directly if you also need translation or scale.
#[inline]
pub fn rotation(&self) -> Quat {
self.to_scale_rotation_translation().1
}
/// Get the scale as a [`Vec3`].
///
/// The transform is expected to be non-degenerate and without shearing, or the output will be invalid.
///
/// Some of the computations overlap with `to_scale_rotation_translation`, which means you should use
/// it instead if you also need rotation.
#[inline]
pub fn scale(&self) -> Vec3 {
//Formula based on glam's implementation https://github.com/bitshifter/glam-rs/blob/2e4443e70c709710dfb25958d866d29b11ed3e2b/src/f32/affine3a.rs#L290
let det = self.0.matrix3.determinant();
Vec3::new(
self.0.matrix3.x_axis.length() * ops::copysign(1., det),
self.0.matrix3.y_axis.length(),
self.0.matrix3.z_axis.length(),
)
}
/// Get an upper bound of the radius from the given `extents`.
#[inline]
pub fn radius_vec3a(&self, extents: Vec3A) -> f32 {
(self.0.matrix3 * extents).length()
}
/// Transforms the given point from local space to global space, applying shear, scale, rotation and translation.
///
/// It can be used like this:
///
/// ```
/// # use bevy_transform::prelude::{GlobalTransform};
/// # use bevy_math::prelude::Vec3;
/// let global_transform = GlobalTransform::from_xyz(1., 2., 3.);
/// let local_point = Vec3::new(1., 2., 3.);
/// let global_point = global_transform.transform_point(local_point);
/// assert_eq!(global_point, Vec3::new(2., 4., 6.));
/// ```
///
/// ```
/// # use bevy_transform::prelude::{GlobalTransform};
/// # use bevy_math::Vec3;
/// let global_point = Vec3::new(2., 4., 6.);
/// let global_transform = GlobalTransform::from_xyz(1., 2., 3.);
/// let local_point = global_transform.affine().inverse().transform_point3(global_point);
/// assert_eq!(local_point, Vec3::new(1., 2., 3.))
/// ```
///
/// To apply shear, scale, and rotation *without* applying translation, different functions are available:
/// ```
/// # use bevy_transform::prelude::{GlobalTransform};
/// # use bevy_math::prelude::Vec3;
/// let global_transform = GlobalTransform::from_xyz(1., 2., 3.);
/// let local_direction = Vec3::new(1., 2., 3.);
/// let global_direction = global_transform.affine().transform_vector3(local_direction);
/// assert_eq!(global_direction, Vec3::new(1., 2., 3.));
/// let roundtripped_local_direction = global_transform.affine().inverse().transform_vector3(global_direction);
/// assert_eq!(roundtripped_local_direction, local_direction);
/// ```
#[inline]
pub fn transform_point(&self, point: Vec3) -> Vec3 {
self.0.transform_point3(point)
}
/// Multiplies `self` with `transform` component by component, returning the
/// resulting [`GlobalTransform`]
#[inline]
pub fn mul_transform(&self, transform: Transform) -> Self {
Self(self.0 * transform.compute_affine())
}
}
impl Default for GlobalTransform {
fn default() -> Self {
Self::IDENTITY
}
}
impl From<Transform> for GlobalTransform {
fn from(transform: Transform) -> Self {
Self(transform.compute_affine())
}
}
impl From<Mat4> for GlobalTransform {
fn from(world_from_local: Mat4) -> Self {
Self(Affine3A::from_mat4(world_from_local))
}
}
impl Mul<GlobalTransform> for GlobalTransform {
type Output = GlobalTransform;
#[inline]
fn mul(self, global_transform: GlobalTransform) -> Self::Output {
GlobalTransform(self.0 * global_transform.0)
}
}
impl Mul<Transform> for GlobalTransform {
type Output = GlobalTransform;
#[inline]
fn mul(self, transform: Transform) -> Self::Output {
self.mul_transform(transform)
}
}
impl Mul<Vec3> for GlobalTransform {
type Output = Vec3;
#[inline]
fn mul(self, value: Vec3) -> Self::Output {
self.transform_point(value)
}
}
#[cfg(test)]
mod test {
use super::*;
use bevy_math::EulerRot::XYZ;
fn transform_equal(left: GlobalTransform, right: Transform) -> bool {
left.0.abs_diff_eq(right.compute_affine(), 0.01)
}
#[test]
fn reparented_to_transform_identity() {
fn reparent_to_same(t1: GlobalTransform, t2: GlobalTransform) -> Transform {
t2.mul_transform(t1.into()).reparented_to(&t2)
}
let t1 = GlobalTransform::from(Transform {
translation: Vec3::new(1034.0, 34.0, -1324.34),
rotation: Quat::from_euler(XYZ, 1.0, 0.9, 2.1),
scale: Vec3::new(1.0, 1.0, 1.0),
});
let t2 = GlobalTransform::from(Transform {
translation: Vec3::new(0.0, -54.493, 324.34),
rotation: Quat::from_euler(XYZ, 1.9, 0.3, 3.0),
scale: Vec3::new(1.345, 1.345, 1.345),
});
let retransformed = reparent_to_same(t1, t2);
assert!(
transform_equal(t1, retransformed),
"t1:{:#?} retransformed:{:#?}",
t1.compute_transform(),
retransformed,
);
}
#[test]
fn reparented_usecase() {
let t1 = GlobalTransform::from(Transform {
translation: Vec3::new(1034.0, 34.0, -1324.34),
rotation: Quat::from_euler(XYZ, 0.8, 1.9, 2.1),
scale: Vec3::new(10.9, 10.9, 10.9),
});
let t2 = GlobalTransform::from(Transform {
translation: Vec3::new(28.0, -54.493, 324.34),
rotation: Quat::from_euler(XYZ, 0.0, 3.1, 0.1),
scale: Vec3::new(0.9, 0.9, 0.9),
});
// goal: find `X` such as `t2 * X = t1`
let reparented = t1.reparented_to(&t2);
let t1_prime = t2 * reparented;
assert!(
transform_equal(t1, t1_prime.into()),
"t1:{:#?} t1_prime:{:#?}",
t1.compute_transform(),
t1_prime.compute_transform(),
);
}
#[test]
fn scale() {
let test_values = [-42.42, 0., 42.42];
for x in test_values {
for y in test_values {
for z in test_values {
let scale = Vec3::new(x, y, z);
let gt = GlobalTransform::from_scale(scale);
assert_eq!(gt.scale(), gt.to_scale_rotation_translation().0);
}
}
}
}
}

View File

@@ -0,0 +1,5 @@
mod global_transform;
mod transform;
pub use global_transform::*;
pub use transform::*;

View File

@@ -0,0 +1,667 @@
use super::GlobalTransform;
use bevy_math::{Affine3A, Dir3, Isometry3d, Mat3, Mat4, Quat, Vec3};
use core::ops::Mul;
#[cfg(feature = "bevy-support")]
use bevy_ecs::component::Component;
#[cfg(feature = "bevy_reflect")]
use {bevy_ecs::reflect::ReflectComponent, bevy_reflect::prelude::*};
/// Checks that a vector with the given squared length is normalized.
///
/// Warns for small error with a length threshold of approximately `1e-4`,
/// and panics for large error with a length threshold of approximately `1e-2`.
#[cfg(debug_assertions)]
fn assert_is_normalized(message: &str, length_squared: f32) {
use bevy_math::ops;
#[cfg(feature = "std")]
use std::eprintln;
let length_error_squared = ops::abs(length_squared - 1.0);
// Panic for large error and warn for slight error.
if length_error_squared > 2e-2 || length_error_squared.is_nan() {
// Length error is approximately 1e-2 or more.
panic!("Error: {message}",);
} else if length_error_squared > 2e-4 {
// Length error is approximately 1e-4 or more.
#[cfg(feature = "std")]
#[expect(clippy::print_stderr, reason = "Allowed behind `std` feature gate.")]
{
eprintln!("Warning: {message}",);
}
}
}
/// Describe the position of an entity. If the entity has a parent, the position is relative
/// to its parent position.
///
/// * To place or move an entity, you should set its [`Transform`].
/// * To get the global transform of an entity, you should get its [`GlobalTransform`].
/// * To be displayed, an entity must have both a [`Transform`] and a [`GlobalTransform`].
/// [`GlobalTransform`] is automatically inserted whenever [`Transform`] is inserted.
///
/// ## [`Transform`] and [`GlobalTransform`]
///
/// [`Transform`] is the position of an entity relative to its parent position, or the reference
/// frame if it doesn't have a [`ChildOf`](bevy_ecs::hierarchy::ChildOf) component.
///
/// [`GlobalTransform`] is the position of an entity relative to the reference frame.
///
/// [`GlobalTransform`] is updated from [`Transform`] by systems in the system set
/// [`TransformPropagate`](crate::TransformSystem::TransformPropagate).
///
/// This system runs during [`PostUpdate`](bevy_app::PostUpdate). If you
/// update the [`Transform`] of an entity during this set or after, you will notice a 1 frame lag
/// before the [`GlobalTransform`] is updated.
///
/// # Examples
///
/// - [`transform`][transform_example]
///
/// [transform_example]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/transform.rs
#[derive(Debug, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "bevy-support",
derive(Component),
require(GlobalTransform, TransformTreeChanged)
)]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Default, PartialEq, Debug, Clone)
)]
#[cfg_attr(
all(feature = "bevy_reflect", feature = "serialize"),
reflect(Serialize, Deserialize)
)]
pub struct Transform {
/// Position of the entity. In 2d, the last value of the `Vec3` is used for z-ordering.
///
/// See the [`translations`] example for usage.
///
/// [`translations`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/translation.rs
pub translation: Vec3,
/// Rotation of the entity.
///
/// See the [`3d_rotation`] example for usage.
///
/// [`3d_rotation`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/3d_rotation.rs
pub rotation: Quat,
/// Scale of the entity.
///
/// See the [`scale`] example for usage.
///
/// [`scale`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/scale.rs
pub scale: Vec3,
}
impl Transform {
/// An identity [`Transform`] with no translation, rotation, and a scale of 1 on all axes.
pub const IDENTITY: Self = Transform {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
/// Creates a new [`Transform`] at the position `(x, y, z)`. In 2d, the `z` component
/// is used for z-ordering elements: higher `z`-value will be in front of lower
/// `z`-value.
#[inline]
pub const fn from_xyz(x: f32, y: f32, z: f32) -> Self {
Self::from_translation(Vec3::new(x, y, z))
}
/// Extracts the translation, rotation, and scale from `matrix`. It must be a 3d affine
/// transformation matrix.
#[inline]
pub fn from_matrix(world_from_local: Mat4) -> Self {
let (scale, rotation, translation) = world_from_local.to_scale_rotation_translation();
Transform {
translation,
rotation,
scale,
}
}
/// Creates a new [`Transform`], with `translation`. Rotation will be 0 and scale 1 on
/// all axes.
#[inline]
pub const fn from_translation(translation: Vec3) -> Self {
Transform {
translation,
..Self::IDENTITY
}
}
/// Creates a new [`Transform`], with `rotation`. Translation will be 0 and scale 1 on
/// all axes.
#[inline]
pub const fn from_rotation(rotation: Quat) -> Self {
Transform {
rotation,
..Self::IDENTITY
}
}
/// Creates a new [`Transform`], with `scale`. Translation will be 0 and rotation 0 on
/// all axes.
#[inline]
pub const fn from_scale(scale: Vec3) -> Self {
Transform {
scale,
..Self::IDENTITY
}
}
/// Creates a new [`Transform`] that is equivalent to the given [isometry].
///
/// [isometry]: Isometry3d
#[inline]
pub fn from_isometry(iso: Isometry3d) -> Self {
Transform {
translation: iso.translation.into(),
rotation: iso.rotation,
..Self::IDENTITY
}
}
/// Returns this [`Transform`] with a new rotation so that [`Transform::forward`]
/// points towards the `target` position and [`Transform::up`] points towards `up`.
///
/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
/// * if `target` is the same as the transform translation, `Vec3::Z` is used instead
/// * if `up` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Y` is used instead
/// * if the resulting forward direction is parallel with `up`, an orthogonal vector is used as the "right" direction
#[inline]
#[must_use]
pub fn looking_at(mut self, target: Vec3, up: impl TryInto<Dir3>) -> Self {
self.look_at(target, up);
self
}
/// Returns this [`Transform`] with a new rotation so that [`Transform::forward`]
/// points in the given `direction` and [`Transform::up`] points towards `up`.
///
/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
/// * if `direction` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Z` is used instead
/// * if `up` fails converting to `Dir3`, `Dir3::Y` is used instead
/// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction
#[inline]
#[must_use]
pub fn looking_to(mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) -> Self {
self.look_to(direction, up);
self
}
/// Rotates this [`Transform`] so that the `main_axis` vector, reinterpreted in local coordinates, points
/// in the given `main_direction`, while `secondary_axis` points towards `secondary_direction`.
/// For example, if a spaceship model has its nose pointing in the X-direction in its own local coordinates
/// and its dorsal fin pointing in the Y-direction, then `align(Dir3::X, v, Dir3::Y, w)` will make the spaceship's
/// nose point in the direction of `v`, while the dorsal fin does its best to point in the direction `w`.
///
///
/// In some cases a rotation cannot be constructed. Another axis will be picked in those cases:
/// * if `main_axis` or `main_direction` fail converting to `Dir3` (e.g are zero), `Dir3::X` takes their place
/// * if `secondary_axis` or `secondary_direction` fail converting, `Dir3::Y` takes their place
/// * if `main_axis` is parallel with `secondary_axis` or `main_direction` is parallel with `secondary_direction`,
/// a rotation is constructed which takes `main_axis` to `main_direction` along a great circle, ignoring the secondary
/// counterparts
///
/// See [`Transform::align`] for additional details.
#[inline]
#[must_use]
pub fn aligned_by(
mut self,
main_axis: impl TryInto<Dir3>,
main_direction: impl TryInto<Dir3>,
secondary_axis: impl TryInto<Dir3>,
secondary_direction: impl TryInto<Dir3>,
) -> Self {
self.align(
main_axis,
main_direction,
secondary_axis,
secondary_direction,
);
self
}
/// Returns this [`Transform`] with a new translation.
#[inline]
#[must_use]
pub const fn with_translation(mut self, translation: Vec3) -> Self {
self.translation = translation;
self
}
/// Returns this [`Transform`] with a new rotation.
#[inline]
#[must_use]
pub const fn with_rotation(mut self, rotation: Quat) -> Self {
self.rotation = rotation;
self
}
/// Returns this [`Transform`] with a new scale.
#[inline]
#[must_use]
pub const fn with_scale(mut self, scale: Vec3) -> Self {
self.scale = scale;
self
}
/// Returns the 3d affine transformation matrix from this transforms translation,
/// rotation, and scale.
#[inline]
pub fn compute_matrix(&self) -> Mat4 {
Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
}
/// Returns the 3d affine transformation matrix from this transforms translation,
/// rotation, and scale.
#[inline]
pub fn compute_affine(&self) -> Affine3A {
Affine3A::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
}
/// Get the unit vector in the local `X` direction.
#[inline]
pub fn local_x(&self) -> Dir3 {
// Quat * unit vector is length 1
Dir3::new_unchecked(self.rotation * Vec3::X)
}
/// Equivalent to [`-local_x()`][Transform::local_x()]
#[inline]
pub fn left(&self) -> Dir3 {
-self.local_x()
}
/// Equivalent to [`local_x()`][Transform::local_x()]
#[inline]
pub fn right(&self) -> Dir3 {
self.local_x()
}
/// Get the unit vector in the local `Y` direction.
#[inline]
pub fn local_y(&self) -> Dir3 {
// Quat * unit vector is length 1
Dir3::new_unchecked(self.rotation * Vec3::Y)
}
/// Equivalent to [`local_y()`][Transform::local_y]
#[inline]
pub fn up(&self) -> Dir3 {
self.local_y()
}
/// Equivalent to [`-local_y()`][Transform::local_y]
#[inline]
pub fn down(&self) -> Dir3 {
-self.local_y()
}
/// Get the unit vector in the local `Z` direction.
#[inline]
pub fn local_z(&self) -> Dir3 {
// Quat * unit vector is length 1
Dir3::new_unchecked(self.rotation * Vec3::Z)
}
/// Equivalent to [`-local_z()`][Transform::local_z]
#[inline]
pub fn forward(&self) -> Dir3 {
-self.local_z()
}
/// Equivalent to [`local_z()`][Transform::local_z]
#[inline]
pub fn back(&self) -> Dir3 {
self.local_z()
}
/// Rotates this [`Transform`] by the given rotation.
///
/// If this [`Transform`] has a parent, the `rotation` is relative to the rotation of the parent.
///
/// # Examples
///
/// - [`3d_rotation`]
///
/// [`3d_rotation`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/3d_rotation.rs
#[inline]
pub fn rotate(&mut self, rotation: Quat) {
self.rotation = rotation * self.rotation;
}
/// Rotates this [`Transform`] around the given `axis` by `angle` (in radians).
///
/// If this [`Transform`] has a parent, the `axis` is relative to the rotation of the parent.
///
/// # Warning
///
/// If you pass in an `axis` based on the current rotation (e.g. obtained via [`Transform::local_x`]),
/// floating point errors can accumulate exponentially when applying rotations repeatedly this way. This will
/// result in a denormalized rotation. In this case, it is recommended to normalize the [`Transform::rotation`] after
/// each call to this method.
#[inline]
pub fn rotate_axis(&mut self, axis: Dir3, angle: f32) {
#[cfg(debug_assertions)]
assert_is_normalized(
"The axis given to `Transform::rotate_axis` is not normalized. This may be a result of obtaining \
the axis from the transform. See the documentation of `Transform::rotate_axis` for more details.",
axis.length_squared(),
);
self.rotate(Quat::from_axis_angle(axis.into(), angle));
}
/// Rotates this [`Transform`] around the `X` axis by `angle` (in radians).
///
/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
#[inline]
pub fn rotate_x(&mut self, angle: f32) {
self.rotate(Quat::from_rotation_x(angle));
}
/// Rotates this [`Transform`] around the `Y` axis by `angle` (in radians).
///
/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
#[inline]
pub fn rotate_y(&mut self, angle: f32) {
self.rotate(Quat::from_rotation_y(angle));
}
/// Rotates this [`Transform`] around the `Z` axis by `angle` (in radians).
///
/// If this [`Transform`] has a parent, the axis is relative to the rotation of the parent.
#[inline]
pub fn rotate_z(&mut self, angle: f32) {
self.rotate(Quat::from_rotation_z(angle));
}
/// Rotates this [`Transform`] by the given `rotation`.
///
/// The `rotation` is relative to this [`Transform`]'s current rotation.
#[inline]
pub fn rotate_local(&mut self, rotation: Quat) {
self.rotation *= rotation;
}
/// Rotates this [`Transform`] around its local `axis` by `angle` (in radians).
///
/// # Warning
///
/// If you pass in an `axis` based on the current rotation (e.g. obtained via [`Transform::local_x`]),
/// floating point errors can accumulate exponentially when applying rotations repeatedly this way. This will
/// result in a denormalized rotation. In this case, it is recommended to normalize the [`Transform::rotation`] after
/// each call to this method.
#[inline]
pub fn rotate_local_axis(&mut self, axis: Dir3, angle: f32) {
#[cfg(debug_assertions)]
assert_is_normalized(
"The axis given to `Transform::rotate_axis_local` is not normalized. This may be a result of obtaining \
the axis from the transform. See the documentation of `Transform::rotate_axis_local` for more details.",
axis.length_squared(),
);
self.rotate_local(Quat::from_axis_angle(axis.into(), angle));
}
/// Rotates this [`Transform`] around its local `X` axis by `angle` (in radians).
#[inline]
pub fn rotate_local_x(&mut self, angle: f32) {
self.rotate_local(Quat::from_rotation_x(angle));
}
/// Rotates this [`Transform`] around its local `Y` axis by `angle` (in radians).
#[inline]
pub fn rotate_local_y(&mut self, angle: f32) {
self.rotate_local(Quat::from_rotation_y(angle));
}
/// Rotates this [`Transform`] around its local `Z` axis by `angle` (in radians).
#[inline]
pub fn rotate_local_z(&mut self, angle: f32) {
self.rotate_local(Quat::from_rotation_z(angle));
}
/// Translates this [`Transform`] around a `point` in space.
///
/// If this [`Transform`] has a parent, the `point` is relative to the [`Transform`] of the parent.
#[inline]
pub fn translate_around(&mut self, point: Vec3, rotation: Quat) {
self.translation = point + rotation * (self.translation - point);
}
/// Rotates this [`Transform`] around a `point` in space.
///
/// If this [`Transform`] has a parent, the `point` is relative to the [`Transform`] of the parent.
#[inline]
pub fn rotate_around(&mut self, point: Vec3, rotation: Quat) {
self.translate_around(point, rotation);
self.rotate(rotation);
}
/// Rotates this [`Transform`] so that [`Transform::forward`] points towards the `target` position,
/// and [`Transform::up`] points towards `up`.
///
/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
/// * if `target` is the same as the transform translation, `Vec3::Z` is used instead
/// * if `up` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::Y` is used instead
/// * if the resulting forward direction is parallel with `up`, an orthogonal vector is used as the "right" direction
#[inline]
pub fn look_at(&mut self, target: Vec3, up: impl TryInto<Dir3>) {
self.look_to(target - self.translation, up);
}
/// Rotates this [`Transform`] so that [`Transform::forward`] points in the given `direction`
/// and [`Transform::up`] points towards `up`.
///
/// In some cases it's not possible to construct a rotation. Another axis will be picked in those cases:
/// * if `direction` fails converting to `Dir3` (e.g if it is `Vec3::ZERO`), `Dir3::NEG_Z` is used instead
/// * if `up` fails converting to `Dir3`, `Dir3::Y` is used instead
/// * if `direction` is parallel with `up`, an orthogonal vector is used as the "right" direction
#[inline]
pub fn look_to(&mut self, direction: impl TryInto<Dir3>, up: impl TryInto<Dir3>) {
let back = -direction.try_into().unwrap_or(Dir3::NEG_Z);
let up = up.try_into().unwrap_or(Dir3::Y);
let right = up
.cross(back.into())
.try_normalize()
.unwrap_or_else(|| up.any_orthonormal_vector());
let up = back.cross(right);
self.rotation = Quat::from_mat3(&Mat3::from_cols(right, up, back.into()));
}
/// Rotates this [`Transform`] so that the `main_axis` vector, reinterpreted in local coordinates, points
/// in the given `main_direction`, while `secondary_axis` points towards `secondary_direction`.
///
/// For example, if a spaceship model has its nose pointing in the X-direction in its own local coordinates
/// and its dorsal fin pointing in the Y-direction, then `align(Dir3::X, v, Dir3::Y, w)` will make the spaceship's
/// nose point in the direction of `v`, while the dorsal fin does its best to point in the direction `w`.
///
/// More precisely, the [`Transform::rotation`] produced will be such that:
/// * applying it to `main_axis` results in `main_direction`
/// * applying it to `secondary_axis` produces a vector that lies in the half-plane generated by `main_direction` and
/// `secondary_direction` (with positive contribution by `secondary_direction`)
///
/// [`Transform::look_to`] is recovered, for instance, when `main_axis` is `Dir3::NEG_Z` (the [`Transform::forward`]
/// direction in the default orientation) and `secondary_axis` is `Dir3::Y` (the [`Transform::up`] direction in the default
/// orientation). (Failure cases may differ somewhat.)
///
/// In some cases a rotation cannot be constructed. Another axis will be picked in those cases:
/// * if `main_axis` or `main_direction` fail converting to `Dir3` (e.g are zero), `Dir3::X` takes their place
/// * if `secondary_axis` or `secondary_direction` fail converting, `Dir3::Y` takes their place
/// * if `main_axis` is parallel with `secondary_axis` or `main_direction` is parallel with `secondary_direction`,
/// a rotation is constructed which takes `main_axis` to `main_direction` along a great circle, ignoring the secondary
/// counterparts
///
/// Example
/// ```
/// # use bevy_math::{Dir3, Vec3, Quat};
/// # use bevy_transform::components::Transform;
/// # let mut t1 = Transform::IDENTITY;
/// # let mut t2 = Transform::IDENTITY;
/// t1.align(Dir3::X, Dir3::Y, Vec3::new(1., 1., 0.), Dir3::Z);
/// let main_axis_image = t1.rotation * Dir3::X;
/// let secondary_axis_image = t1.rotation * Vec3::new(1., 1., 0.);
/// assert!(main_axis_image.abs_diff_eq(Vec3::Y, 1e-5));
/// assert!(secondary_axis_image.abs_diff_eq(Vec3::new(0., 1., 1.), 1e-5));
///
/// t1.align(Vec3::ZERO, Dir3::Z, Vec3::ZERO, Dir3::X);
/// t2.align(Dir3::X, Dir3::Z, Dir3::Y, Dir3::X);
/// assert_eq!(t1.rotation, t2.rotation);
///
/// t1.align(Dir3::X, Dir3::Z, Dir3::X, Dir3::Y);
/// assert_eq!(t1.rotation, Quat::from_rotation_arc(Vec3::X, Vec3::Z));
/// ```
#[inline]
pub fn align(
&mut self,
main_axis: impl TryInto<Dir3>,
main_direction: impl TryInto<Dir3>,
secondary_axis: impl TryInto<Dir3>,
secondary_direction: impl TryInto<Dir3>,
) {
let main_axis = main_axis.try_into().unwrap_or(Dir3::X);
let main_direction = main_direction.try_into().unwrap_or(Dir3::X);
let secondary_axis = secondary_axis.try_into().unwrap_or(Dir3::Y);
let secondary_direction = secondary_direction.try_into().unwrap_or(Dir3::Y);
// The solution quaternion will be constructed in two steps.
// First, we start with a rotation that takes `main_axis` to `main_direction`.
let first_rotation = Quat::from_rotation_arc(main_axis.into(), main_direction.into());
// Let's follow by rotating about the `main_direction` axis so that the image of `secondary_axis`
// is taken to something that lies in the plane of `main_direction` and `secondary_direction`. Since
// `main_direction` is fixed by this rotation, the first criterion is still satisfied.
let secondary_image = first_rotation * secondary_axis;
let secondary_image_ortho = secondary_image
.reject_from_normalized(main_direction.into())
.try_normalize();
let secondary_direction_ortho = secondary_direction
.reject_from_normalized(main_direction.into())
.try_normalize();
// If one of the two weak vectors was parallel to `main_direction`, then we just do the first part
self.rotation = match (secondary_image_ortho, secondary_direction_ortho) {
(Some(secondary_img_ortho), Some(secondary_dir_ortho)) => {
let second_rotation =
Quat::from_rotation_arc(secondary_img_ortho, secondary_dir_ortho);
second_rotation * first_rotation
}
_ => first_rotation,
};
}
/// Multiplies `self` with `transform` component by component, returning the
/// resulting [`Transform`]
#[inline]
#[must_use]
pub fn mul_transform(&self, transform: Transform) -> Self {
let translation = self.transform_point(transform.translation);
let rotation = self.rotation * transform.rotation;
let scale = self.scale * transform.scale;
Transform {
translation,
rotation,
scale,
}
}
/// Transforms the given `point`, applying scale, rotation and translation.
///
/// If this [`Transform`] has an ancestor entity with a [`Transform`] component,
/// [`Transform::transform_point`] will transform a point in local space into its
/// parent transform's space.
///
/// If this [`Transform`] does not have a parent, [`Transform::transform_point`] will
/// transform a point in local space into worldspace coordinates.
///
/// If you always want to transform a point in local space to worldspace, or if you need
/// the inverse transformations, see [`GlobalTransform::transform_point()`].
#[inline]
pub fn transform_point(&self, mut point: Vec3) -> Vec3 {
point = self.scale * point;
point = self.rotation * point;
point += self.translation;
point
}
/// Returns `true` if, and only if, translation, rotation and scale all are
/// finite. If any of them contains a `NaN`, positive or negative infinity,
/// this will return `false`.
#[inline]
#[must_use]
pub fn is_finite(&self) -> bool {
self.translation.is_finite() && self.rotation.is_finite() && self.scale.is_finite()
}
/// Get the [isometry] defined by this transform's rotation and translation, ignoring scale.
///
/// [isometry]: Isometry3d
#[inline]
pub fn to_isometry(&self) -> Isometry3d {
Isometry3d::new(self.translation, self.rotation)
}
}
impl Default for Transform {
fn default() -> Self {
Self::IDENTITY
}
}
/// The transform is expected to be non-degenerate and without shearing, or the output
/// will be invalid.
impl From<GlobalTransform> for Transform {
fn from(transform: GlobalTransform) -> Self {
transform.compute_transform()
}
}
impl Mul<Transform> for Transform {
type Output = Transform;
fn mul(self, transform: Transform) -> Self::Output {
self.mul_transform(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;
fn mul(self, value: Vec3) -> Self::Output {
self.transform_point(value)
}
}
/// An optimization for transform propagation. This ZST marker component uses change detection to
/// mark all entities of the hierarchy as "dirty" if any of their descendants have a changed
/// `Transform`. If this component is *not* marked `is_changed()`, propagation will halt.
#[derive(Clone, Copy, Default, PartialEq, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy-support", derive(Component))]
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Default, PartialEq, Debug)
)]
#[cfg_attr(
all(feature = "bevy_reflect", feature = "serialize"),
reflect(Serialize, Deserialize)
)]
pub struct TransformTreeChanged;

146
vendor/bevy_transform/src/helper.rs vendored Normal file
View File

@@ -0,0 +1,146 @@
//! 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());
}
}

54
vendor/bevy_transform/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,54 @@
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
)]
#![no_std]
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "bevy-support")]
pub mod commands;
/// The basic components of the transform crate
pub mod components;
/// Transform related traits
pub mod traits;
/// Transform related plugins
#[cfg(feature = "bevy-support")]
pub mod plugins;
/// [`GlobalTransform`]: components::GlobalTransform
/// Helpers related to computing global transforms
#[cfg(feature = "bevy-support")]
pub mod helper;
/// Systems responsible for transform propagation
#[cfg(feature = "bevy-support")]
pub mod systems;
/// The transform prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
#[doc(hidden)]
pub mod prelude {
#[doc(hidden)]
pub use crate::components::*;
#[cfg(feature = "bevy-support")]
#[doc(hidden)]
pub use crate::{
commands::BuildChildrenTransformExt,
helper::TransformHelper,
plugins::{TransformPlugin, TransformSystem},
traits::TransformPoint,
};
}
#[cfg(feature = "bevy-support")]
pub use prelude::{TransformPlugin, TransformPoint, TransformSystem};

47
vendor/bevy_transform/src/plugins.rs vendored Normal file
View File

@@ -0,0 +1,47 @@
use crate::systems::{mark_dirty_trees, propagate_parent_transforms, sync_simple_transforms};
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
use bevy_ecs::schedule::{IntoScheduleConfigs, SystemSet};
/// Set enum for the systems relating to transform propagation
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum TransformSystem {
/// Propagates changes in transform to children's [`GlobalTransform`](crate::components::GlobalTransform)
TransformPropagate,
}
/// The base plugin for handling [`Transform`](crate::components::Transform) components
#[derive(Default)]
pub struct TransformPlugin;
impl Plugin for TransformPlugin {
fn build(&self, app: &mut App) {
#[cfg(feature = "bevy_reflect")]
app.register_type::<crate::components::Transform>()
.register_type::<crate::components::TransformTreeChanged>()
.register_type::<crate::components::GlobalTransform>();
app
// add transform systems to startup so the first update is "correct"
.add_systems(
PostStartup,
(
mark_dirty_trees,
propagate_parent_transforms,
sync_simple_transforms,
)
.chain()
.in_set(TransformSystem::TransformPropagate),
)
.add_systems(
PostUpdate,
(
mark_dirty_trees,
propagate_parent_transforms,
// TODO: Adjust the internal parallel queries to make this system more efficiently share and fill CPU time.
sync_simple_transforms,
)
.chain()
.in_set(TransformSystem::TransformPropagate),
);
}
}

956
vendor/bevy_transform/src/systems.rs vendored Normal file
View File

@@ -0,0 +1,956 @@
use crate::components::{GlobalTransform, Transform, TransformTreeChanged};
use bevy_ecs::prelude::*;
#[cfg(feature = "std")]
pub use parallel::propagate_parent_transforms;
#[cfg(not(feature = "std"))]
pub use serial::propagate_parent_transforms;
/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy
///
/// Third party plugins should ensure that this is used in concert with
/// [`propagate_parent_transforms`] and [`mark_dirty_trees`].
pub fn sync_simple_transforms(
mut query: ParamSet<(
Query<
(&Transform, &mut GlobalTransform),
(
Or<(Changed<Transform>, Added<GlobalTransform>)>,
Without<ChildOf>,
Without<Children>,
),
>,
Query<(Ref<Transform>, &mut GlobalTransform), (Without<ChildOf>, Without<Children>)>,
)>,
mut orphaned: RemovedComponents<ChildOf>,
) {
// Update changed entities.
query
.p0()
.par_iter_mut()
.for_each(|(transform, mut global_transform)| {
*global_transform = GlobalTransform::from(*transform);
});
// Update orphaned entities.
let mut query = query.p1();
let mut iter = query.iter_many_mut(orphaned.read());
while let Some((transform, mut global_transform)) = iter.fetch_next() {
if !transform.is_changed() && !global_transform.is_added() {
*global_transform = GlobalTransform::from(*transform);
}
}
}
/// Optimization for static scenes. Propagates a "dirty bit" up the hierarchy towards ancestors.
/// Transform propagation can ignore entire subtrees of the hierarchy if it encounters an entity
/// without the dirty bit.
pub fn mark_dirty_trees(
changed_transforms: Query<
Entity,
Or<(Changed<Transform>, Changed<ChildOf>, Added<GlobalTransform>)>,
>,
mut orphaned: RemovedComponents<ChildOf>,
mut transforms: Query<(Option<&ChildOf>, &mut TransformTreeChanged)>,
) {
for entity in changed_transforms.iter().chain(orphaned.read()) {
let mut next = entity;
while let Ok((child_of, mut tree)) = transforms.get_mut(next) {
if tree.is_changed() && !tree.is_added() {
// If the component was changed, this part of the tree has already been processed.
// Ignore this if the change was caused by the component being added.
break;
}
tree.set_changed();
if let Some(parent) = child_of.map(ChildOf::parent) {
next = parent;
} else {
break;
};
}
}
}
// TODO: This serial implementation isn't actually serial, it parallelizes across the roots.
// Additionally, this couples "no_std" with "single_threaded" when these two features should be
// independent.
//
// What we want to do in a future refactor is take the current "single threaded" implementation, and
// actually make it single threaded. This will remove any overhead associated with working on a task
// pool when you only have a single thread, and will have the benefit of removing the need for any
// unsafe. We would then make the multithreaded implementation work across std and no_std, but this
// is blocked a no_std compatible Channel, which is why this TODO is not yet implemented.
//
// This complexity might also not be needed. If the multithreaded implementation on a single thread
// is as fast as the single threaded implementation, we could simply remove the entire serial
// module, and make the multithreaded module no_std compatible.
//
/// Serial hierarchy traversal. Useful in `no_std` or single threaded contexts.
#[cfg(not(feature = "std"))]
mod serial {
use crate::prelude::*;
use alloc::vec::Vec;
use bevy_ecs::prelude::*;
/// Update [`GlobalTransform`] component of entities based on entity hierarchy and [`Transform`]
/// component.
///
/// Third party plugins should ensure that this is used in concert with
/// [`sync_simple_transforms`](super::sync_simple_transforms) and
/// [`mark_dirty_trees`](super::mark_dirty_trees).
pub fn propagate_parent_transforms(
mut root_query: Query<
(Entity, &Children, Ref<Transform>, &mut GlobalTransform),
Without<ChildOf>,
>,
mut orphaned: RemovedComponents<ChildOf>,
transform_query: Query<
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
With<ChildOf>,
>,
child_query: Query<(Entity, Ref<ChildOf>), With<GlobalTransform>>,
mut orphaned_entities: Local<Vec<Entity>>,
) {
orphaned_entities.clear();
orphaned_entities.extend(orphaned.read());
orphaned_entities.sort_unstable();
root_query.par_iter_mut().for_each(
|(entity, children, transform, mut global_transform)| {
let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
if changed {
*global_transform = GlobalTransform::from(*transform);
}
for (child, child_of) in child_query.iter_many(children) {
assert_eq!(
child_of.parent(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
// SAFETY:
// - `child` must have consistent parentage, or the above assertion would panic.
// Since `child` is parented to a root entity, the entire hierarchy leading to it
// is consistent.
// - We may operate as if all descendants are consistent, since
// `propagate_recursive` will panic before continuing to propagate if it
// encounters an entity with inconsistent parentage.
// - Since each root entity is unique and the hierarchy is consistent and
// forest-like, other root entities' `propagate_recursive` calls will not conflict
// with this one.
// - Since this is the only place where `transform_query` gets used, there will be
// no conflicting fetches elsewhere.
#[expect(unsafe_code, reason = "`propagate_recursive()` is unsafe due to its use of `Query::get_unchecked()`.")]
unsafe {
propagate_recursive(
&global_transform,
&transform_query,
&child_query,
child,
changed || child_of.is_changed(),
);
}
}
},
);
}
/// Recursively propagates the transforms for `entity` and all of its descendants.
///
/// # Panics
///
/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before
/// propagating the transforms of any malformed entities and their descendants.
///
/// # Safety
///
/// - While this function is running, `transform_query` must not have any fetches for `entity`,
/// nor any of its descendants.
/// - The caller must ensure that the hierarchy leading to `entity` is well-formed and must
/// remain as a tree or a forest. Each entity must have at most one parent.
#[expect(
unsafe_code,
reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met."
)]
unsafe fn propagate_recursive(
parent: &GlobalTransform,
transform_query: &Query<
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
With<ChildOf>,
>,
child_query: &Query<(Entity, Ref<ChildOf>), With<GlobalTransform>>,
entity: Entity,
mut changed: bool,
) {
let (global_matrix, children) = {
let Ok((transform, mut global_transform, children)) =
// SAFETY: This call cannot create aliased mutable references.
// - The top level iteration parallelizes on the roots of the hierarchy.
// - The caller ensures that each child has one and only one unique parent throughout
// the entire hierarchy.
//
// For example, consider the following malformed hierarchy:
//
// A
// / \
// B C
// \ /
// D
//
// D has two parents, B and C. If the propagation passes through C, but the ChildOf
// component on D points to B, the above check will panic as the origin parent does
// match the recorded parent.
//
// Also consider the following case, where A and B are roots:
//
// A B
// \ /
// C D
// \ /
// E
//
// Even if these A and B start two separate tasks running in parallel, one of them will
// panic before attempting to mutably access E.
(unsafe { transform_query.get_unchecked(entity) }) else {
return;
};
changed |= transform.is_changed() || global_transform.is_added();
if changed {
*global_transform = parent.mul_transform(*transform);
}
(global_transform, children)
};
let Some(children) = children else { return };
for (child, child_of) in child_query.iter_many(children) {
assert_eq!(
child_of.parent(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
);
// SAFETY: The caller guarantees that `transform_query` will not be fetched for any
// descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
//
// The above assertion ensures that each child has one and only one unique parent
// throughout the entire hierarchy.
unsafe {
propagate_recursive(
global_matrix.as_ref(),
transform_query,
child_query,
child,
changed || child_of.is_changed(),
);
}
}
}
}
// TODO: Relies on `std` until a `no_std` `mpsc` channel is available.
//
/// Parallel hierarchy traversal with a batched work sharing scheduler. Often 2-5 times faster than
/// the serial version.
#[cfg(feature = "std")]
mod parallel {
use crate::prelude::*;
// TODO: this implementation could be used in no_std if there are equivalents of these.
use alloc::{sync::Arc, vec::Vec};
use bevy_ecs::{entity::UniqueEntityIter, prelude::*, system::lifetimeless::Read};
use bevy_tasks::{ComputeTaskPool, TaskPool};
use bevy_utils::Parallel;
use core::sync::atomic::{AtomicI32, Ordering};
use std::sync::{
mpsc::{Receiver, Sender},
Mutex,
};
/// Update [`GlobalTransform`] component of entities based on entity hierarchy and [`Transform`]
/// component.
///
/// Third party plugins should ensure that this is used in concert with
/// [`sync_simple_transforms`](super::sync_simple_transforms) and
/// [`mark_dirty_trees`](super::mark_dirty_trees).
pub fn propagate_parent_transforms(
mut queue: Local<WorkQueue>,
mut roots: Query<
(Entity, Ref<Transform>, &mut GlobalTransform, &Children),
(Without<ChildOf>, Changed<TransformTreeChanged>),
>,
nodes: NodeQuery,
) {
// Process roots in parallel, seeding the work queue
roots.par_iter_mut().for_each_init(
|| queue.local_queue.borrow_local_mut(),
|outbox, (parent, transform, mut parent_transform, children)| {
*parent_transform = GlobalTransform::from(*transform);
// SAFETY: the parent entities passed into this function are taken from iterating
// over the root entity query. Queries iterate over disjoint entities, preventing
// mutable aliasing, and making this call safe.
#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
unsafe {
propagate_descendants_unchecked(
parent,
parent_transform,
children,
&nodes,
outbox,
&queue,
// Need to revisit this single-max-depth by profiling more representative
// scenes. It's possible that it is actually beneficial to go deep into the
// hierarchy to build up a good task queue before starting the workers.
// However, we avoid this for now to prevent cases where only a single
// thread is going deep into the hierarchy while the others sit idle, which
// is the problem that the tasks sharing workers already solve.
1,
);
}
},
);
// Send all tasks in thread local outboxes *after* roots are processed to reduce the total
// number of channel sends by avoiding sending partial batches.
queue.send_batches();
if let Ok(rx) = queue.receiver.try_lock() {
if let Some(task) = rx.try_iter().next() {
// This is a bit silly, but the only way to see if there is any work is to grab a
// task. Peeking will remove the task even if you don't call `next`, resulting in
// dropping a task. What we do here is grab the first task if there is one, then
// immediately send it to the back of the queue.
queue.sender.send(task).ok();
} else {
return; // No work, don't bother spawning any tasks
}
}
// Spawn workers on the task pool to recursively propagate the hierarchy in parallel.
let task_pool = ComputeTaskPool::get_or_init(TaskPool::default);
task_pool.scope(|s| {
(1..task_pool.thread_num()) // First worker is run locally instead of the task pool.
.for_each(|_| s.spawn(async { propagation_worker(&queue, &nodes) }));
propagation_worker(&queue, &nodes);
});
}
/// A parallel worker that will consume processed parent entities from the queue, and push
/// children to the queue once it has propagated their [`GlobalTransform`].
#[inline]
fn propagation_worker(queue: &WorkQueue, nodes: &NodeQuery) {
#[cfg(feature = "std")]
let _span = bevy_log::info_span!("transform propagation worker").entered();
let mut outbox = queue.local_queue.borrow_local_mut();
loop {
// Try to acquire a lock on the work queue in a tight loop. Profiling shows this is much
// more efficient than relying on `.lock()`, which causes gaps to form between tasks.
let Ok(rx) = queue.receiver.try_lock() else {
core::hint::spin_loop(); // No apparent impact on profiles, but best practice.
continue;
};
// If the queue is empty and no other threads are busy processing work, we can conclude
// there is no more work to do, and end the task by exiting the loop.
let Some(mut tasks) = rx.try_iter().next() else {
if queue.busy_threads.load(Ordering::Relaxed) == 0 {
break; // All work is complete, kill the worker
}
continue; // No work to do now, but another thread is busy creating more work.
};
if tasks.is_empty() {
continue; // This shouldn't happen, but if it does, we might as well stop early.
}
// If the task queue is extremely short, it's worthwhile to gather a few more tasks to
// reduce the amount of thread synchronization needed once this very short task is
// complete.
while tasks.len() < WorkQueue::CHUNK_SIZE / 2 {
let Some(mut extra_task) = rx.try_iter().next() else {
break;
};
tasks.append(&mut extra_task);
}
// At this point, we know there is work to do, so we increment the busy thread counter,
// and drop the mutex guard *after* we have incremented the counter. This ensures that
// if another thread is able to acquire a lock, the busy thread counter will already be
// incremented.
queue.busy_threads.fetch_add(1, Ordering::Relaxed);
drop(rx); // Important: drop after atomic and before work starts.
for parent in tasks.drain(..) {
// SAFETY: each task pushed to the worker queue represents an unprocessed subtree of
// the hierarchy, guaranteeing unique access.
#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
unsafe {
let (_, (_, p_global_transform, _), (p_children, _)) =
nodes.get_unchecked(parent).unwrap();
propagate_descendants_unchecked(
parent,
p_global_transform,
p_children.unwrap(), // All entities in the queue should have children
nodes,
&mut outbox,
queue,
// Only affects performance. Trees deeper than this will still be fully
// propagated, but the work will be broken into multiple tasks. This number
// was chosen to be larger than any reasonable tree depth, while not being
// so large the function could hang on a deep hierarchy.
10_000,
);
}
}
WorkQueue::send_batches_with(&queue.sender, &mut outbox);
queue.busy_threads.fetch_add(-1, Ordering::Relaxed);
}
}
/// Propagate transforms from `parent` to its `children`, pushing updated child entities to the
/// `outbox`. This function will continue propagating transforms to descendants in a depth-first
/// traversal, while simultaneously pushing unvisited branches to the outbox, for other threads
/// to take when idle.
///
/// # Safety
///
/// Callers must ensure that concurrent calls to this function are given unique `parent`
/// entities. Calling this function concurrently with the same `parent` is unsound. This
/// function will validate that the entity hierarchy does not contain cycles to prevent mutable
/// aliasing during propagation, but it is unable to verify that it isn't being used to mutably
/// alias the same entity.
///
/// ## Panics
///
/// Panics if the parent of a child node is not the same as the supplied `parent`. This
/// assertion ensures that the hierarchy is acyclic, which in turn ensures that if the caller is
/// following the supplied safety rules, multi-threaded propagation is sound.
#[inline]
#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
unsafe fn propagate_descendants_unchecked(
parent: Entity,
p_global_transform: Mut<GlobalTransform>,
p_children: &Children,
nodes: &NodeQuery,
outbox: &mut Vec<Entity>,
queue: &WorkQueue,
max_depth: usize,
) {
// Create mutable copies of the input variables, used for iterative depth-first traversal.
let (mut parent, mut p_global_transform, mut p_children) =
(parent, p_global_transform, p_children);
// See the optimization note at the end to understand why this loop is here.
for depth in 1..=max_depth {
// Safety: traversing the entity tree from the roots, we assert that the childof and
// children pointers match in both directions (see assert below) to ensure the hierarchy
// does not have any cycles. Because the hierarchy does not have cycles, we know we are
// visiting disjoint entities in parallel, which is safe.
#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
let children_iter = unsafe {
nodes.iter_many_unique_unsafe(UniqueEntityIter::from_iterator_unchecked(
p_children.iter(),
))
};
let mut last_child = None;
let new_children = children_iter.filter_map(
|(child, (transform, mut global_transform, tree), (children, child_of))| {
if !tree.is_changed() && !p_global_transform.is_changed() {
// Static scene optimization
return None;
}
assert_eq!(child_of.parent(), parent);
// Transform prop is expensive - this helps avoid updating entire subtrees if
// the GlobalTransform is unchanged, at the cost of an added equality check.
global_transform.set_if_neq(p_global_transform.mul_transform(*transform));
children.map(|children| {
// Only continue propagation if the entity has children.
last_child = Some((child, global_transform, children));
child
})
},
);
outbox.extend(new_children);
if depth >= max_depth || last_child.is_none() {
break; // Don't remove anything from the outbox or send any chunks, just exit.
}
// Optimization: tasks should consume work locally as long as they can to avoid
// thread synchronization for as long as possible.
if let Some(last_child) = last_child {
// Overwrite parent data with children, and loop to iterate through descendants.
(parent, p_global_transform, p_children) = last_child;
outbox.pop();
// Send chunks during traversal. This allows sharing tasks with other threads before
// fully completing the traversal.
if outbox.len() >= WorkQueue::CHUNK_SIZE {
WorkQueue::send_batches_with(&queue.sender, outbox);
}
}
}
}
/// Alias for a large, repeatedly used query. Queries for transform entities that have both a
/// parent and possibly children, thus they are not roots.
type NodeQuery<'w, 's> = Query<
'w,
's,
(
Entity,
(
Ref<'static, Transform>,
Mut<'static, GlobalTransform>,
Ref<'static, TransformTreeChanged>,
),
(Option<Read<Children>>, Read<ChildOf>),
),
>;
/// A queue shared between threads for transform propagation.
pub struct WorkQueue {
/// A semaphore that tracks how many threads are busy doing work. Used to determine when
/// there is no more work to do.
busy_threads: AtomicI32,
sender: Sender<Vec<Entity>>,
receiver: Arc<Mutex<Receiver<Vec<Entity>>>>,
local_queue: Parallel<Vec<Entity>>,
}
impl Default for WorkQueue {
fn default() -> Self {
let (tx, rx) = std::sync::mpsc::channel();
Self {
busy_threads: AtomicI32::default(),
sender: tx,
receiver: Arc::new(Mutex::new(rx)),
local_queue: Default::default(),
}
}
}
impl WorkQueue {
const CHUNK_SIZE: usize = 512;
#[inline]
fn send_batches_with(sender: &Sender<Vec<Entity>>, outbox: &mut Vec<Entity>) {
for chunk in outbox
.chunks(WorkQueue::CHUNK_SIZE)
.filter(|c| !c.is_empty())
{
sender.send(chunk.to_vec()).ok();
}
outbox.clear();
}
#[inline]
fn send_batches(&mut self) {
let Self {
sender,
local_queue,
..
} = self;
// Iterate over the locals to send batched tasks, avoiding the need to drain the locals
// into a larger allocation.
local_queue
.iter_mut()
.for_each(|outbox| Self::send_batches_with(sender, outbox));
}
}
}
#[cfg(test)]
mod test {
use alloc::{vec, vec::Vec};
use bevy_app::prelude::*;
use bevy_ecs::{prelude::*, world::CommandQueue};
use bevy_math::{vec3, Vec3};
use bevy_tasks::{ComputeTaskPool, TaskPool};
use crate::systems::*;
#[test]
fn correct_parent_removed() {
ComputeTaskPool::get_or_init(TaskPool::default);
let mut world = World::default();
let offset_global_transform =
|offset| GlobalTransform::from(Transform::from_xyz(offset, offset, offset));
let offset_transform = |offset| Transform::from_xyz(offset, offset, offset);
let mut schedule = Schedule::default();
schedule.add_systems(
(
mark_dirty_trees,
sync_simple_transforms,
propagate_parent_transforms,
)
.chain(),
);
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, &world);
let root = commands.spawn(offset_transform(3.3)).id();
let parent = commands.spawn(offset_transform(4.4)).id();
let child = commands.spawn(offset_transform(5.5)).id();
commands.entity(parent).insert(ChildOf(root));
commands.entity(child).insert(ChildOf(parent));
command_queue.apply(&mut world);
schedule.run(&mut world);
assert_eq!(
world.get::<GlobalTransform>(parent).unwrap(),
&offset_global_transform(4.4 + 3.3),
"The transform systems didn't run, ie: `GlobalTransform` wasn't updated",
);
// Remove parent of `parent`
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, &world);
commands.entity(parent).remove::<ChildOf>();
command_queue.apply(&mut world);
schedule.run(&mut world);
assert_eq!(
world.get::<GlobalTransform>(parent).unwrap(),
&offset_global_transform(4.4),
"The global transform of an orphaned entity wasn't updated properly",
);
// Remove parent of `child`
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, &world);
commands.entity(child).remove::<ChildOf>();
command_queue.apply(&mut world);
schedule.run(&mut world);
assert_eq!(
world.get::<GlobalTransform>(child).unwrap(),
&offset_global_transform(5.5),
"The global transform of an orphaned entity wasn't updated properly",
);
}
#[test]
fn did_propagate() {
ComputeTaskPool::get_or_init(TaskPool::default);
let mut world = World::default();
let mut schedule = Schedule::default();
schedule.add_systems(
(
mark_dirty_trees,
sync_simple_transforms,
propagate_parent_transforms,
)
.chain(),
);
// Root entity
world.spawn(Transform::from_xyz(1.0, 0.0, 0.0));
let mut children = Vec::new();
world
.spawn(Transform::from_xyz(1.0, 0.0, 0.0))
.with_children(|parent| {
children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.)).id());
children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.)).id());
});
schedule.run(&mut world);
assert_eq!(
*world.get::<GlobalTransform>(children[0]).unwrap(),
GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
);
assert_eq!(
*world.get::<GlobalTransform>(children[1]).unwrap(),
GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
);
}
#[test]
fn did_propagate_command_buffer() {
let mut world = World::default();
let mut schedule = Schedule::default();
schedule.add_systems(
(
mark_dirty_trees,
sync_simple_transforms,
propagate_parent_transforms,
)
.chain(),
);
// Root entity
let mut queue = CommandQueue::default();
let mut commands = Commands::new(&mut queue, &world);
let mut children = Vec::new();
commands
.spawn(Transform::from_xyz(1.0, 0.0, 0.0))
.with_children(|parent| {
children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.0)).id());
});
queue.apply(&mut world);
schedule.run(&mut world);
assert_eq!(
*world.get::<GlobalTransform>(children[0]).unwrap(),
GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
);
assert_eq!(
*world.get::<GlobalTransform>(children[1]).unwrap(),
GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
);
}
#[test]
fn correct_children() {
ComputeTaskPool::get_or_init(TaskPool::default);
let mut world = World::default();
let mut schedule = Schedule::default();
schedule.add_systems(
(
mark_dirty_trees,
sync_simple_transforms,
propagate_parent_transforms,
)
.chain(),
);
// Add parent entities
let mut children = Vec::new();
let parent = {
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, &world);
let parent = commands.spawn(Transform::from_xyz(1.0, 0.0, 0.0)).id();
commands.entity(parent).with_children(|parent| {
children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
children.push(parent.spawn(Transform::from_xyz(0.0, 3.0, 0.0)).id());
});
command_queue.apply(&mut world);
schedule.run(&mut world);
parent
};
assert_eq!(
world
.get::<Children>(parent)
.unwrap()
.iter()
.collect::<Vec<_>>(),
children,
);
// Parent `e1` to `e2`.
{
let mut command_queue = CommandQueue::default();
let mut commands = Commands::new(&mut command_queue, &world);
commands.entity(children[1]).add_child(children[0]);
command_queue.apply(&mut world);
schedule.run(&mut world);
}
assert_eq!(
world
.get::<Children>(parent)
.unwrap()
.iter()
.collect::<Vec<_>>(),
vec![children[1]]
);
assert_eq!(
world
.get::<Children>(children[1])
.unwrap()
.iter()
.collect::<Vec<_>>(),
vec![children[0]]
);
assert!(world.despawn(children[0]));
schedule.run(&mut world);
assert_eq!(
world
.get::<Children>(parent)
.unwrap()
.iter()
.collect::<Vec<_>>(),
vec![children[1]]
);
}
#[test]
fn correct_transforms_when_no_children() {
let mut app = App::new();
ComputeTaskPool::get_or_init(TaskPool::default);
app.add_systems(
Update,
(
mark_dirty_trees,
sync_simple_transforms,
propagate_parent_transforms,
)
.chain(),
);
let translation = vec3(1.0, 0.0, 0.0);
// These will be overwritten.
let mut child = Entity::from_raw(0);
let mut grandchild = Entity::from_raw(1);
let parent = app
.world_mut()
.spawn(Transform::from_translation(translation))
.with_children(|builder| {
child = builder
.spawn(Transform::IDENTITY)
.with_children(|builder| {
grandchild = builder.spawn(Transform::IDENTITY).id();
})
.id();
})
.id();
app.update();
// check the `Children` structure is spawned
assert_eq!(&**app.world().get::<Children>(parent).unwrap(), &[child]);
assert_eq!(
&**app.world().get::<Children>(child).unwrap(),
&[grandchild]
);
// Note that at this point, the `GlobalTransform`s will not have updated yet, due to
// `Commands` delay
app.update();
let mut state = app.world_mut().query::<&GlobalTransform>();
for global in state.iter(app.world()) {
assert_eq!(global, &GlobalTransform::from_translation(translation));
}
}
#[test]
#[should_panic]
fn panic_when_hierarchy_cycle() {
ComputeTaskPool::get_or_init(TaskPool::default);
// We cannot directly edit ChildOf and Children, so we use a temp world to break the
// hierarchy's invariants.
let mut temp = World::new();
let mut app = App::new();
app.add_systems(
Update,
// It is unsound for this unsafe system to encounter a cycle without panicking. This
// requirement only applies to systems with unsafe parallel traversal that result in
// aliased mutability during a cycle.
propagate_parent_transforms,
);
fn setup_world(world: &mut World) -> (Entity, Entity) {
let mut grandchild = Entity::from_raw(0);
let child = world
.spawn(Transform::IDENTITY)
.with_children(|builder| {
grandchild = builder.spawn(Transform::IDENTITY).id();
})
.id();
(child, grandchild)
}
let (temp_child, temp_grandchild) = setup_world(&mut temp);
let (child, grandchild) = setup_world(app.world_mut());
assert_eq!(temp_child, child);
assert_eq!(temp_grandchild, grandchild);
app.world_mut()
.spawn(Transform::IDENTITY)
.add_children(&[child]);
let mut child_entity = app.world_mut().entity_mut(child);
let mut grandchild_entity = temp.entity_mut(grandchild);
#[expect(
unsafe_code,
reason = "ChildOf is not mutable but this is for a test to produce a scenario that cannot happen"
)]
// SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that
// cannot happen
let mut a = unsafe { child_entity.get_mut_assume_mutable::<ChildOf>().unwrap() };
// SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that
// cannot happen
#[expect(
unsafe_code,
reason = "ChildOf is not mutable but this is for a test to produce a scenario that cannot happen"
)]
let mut b = unsafe {
grandchild_entity
.get_mut_assume_mutable::<ChildOf>()
.unwrap()
};
core::mem::swap(a.as_mut(), b.as_mut());
app.update();
}
#[test]
fn global_transform_should_not_be_overwritten_after_reparenting() {
let translation = Vec3::ONE;
let mut world = World::new();
// Create transform propagation schedule
let mut schedule = Schedule::default();
schedule.add_systems(
(
mark_dirty_trees,
propagate_parent_transforms,
sync_simple_transforms,
)
.chain(),
);
// Spawn a `Transform` entity with a local translation of `Vec3::ONE`
let mut spawn_transform_bundle =
|| world.spawn(Transform::from_translation(translation)).id();
// Spawn parent and child with identical transform bundles
let parent = spawn_transform_bundle();
let child = spawn_transform_bundle();
world.entity_mut(parent).add_child(child);
// Run schedule to propagate transforms
schedule.run(&mut world);
// Child should be positioned relative to its parent
let parent_global_transform = *world.entity(parent).get::<GlobalTransform>().unwrap();
let child_global_transform = *world.entity(child).get::<GlobalTransform>().unwrap();
assert!(parent_global_transform
.translation()
.abs_diff_eq(translation, 0.1));
assert!(child_global_transform
.translation()
.abs_diff_eq(2. * translation, 0.1));
// Reparent child
world.entity_mut(child).remove::<ChildOf>();
world.entity_mut(parent).add_child(child);
// Run schedule to propagate transforms
schedule.run(&mut world);
// Translations should be unchanged after update
assert_eq!(
parent_global_transform,
*world.entity(parent).get::<GlobalTransform>().unwrap()
);
assert_eq!(
child_global_transform,
*world.entity(child).get::<GlobalTransform>().unwrap()
);
}
}

44
vendor/bevy_transform/src/traits.rs vendored Normal file
View File

@@ -0,0 +1,44 @@
use bevy_math::{Affine3A, Isometry3d, Mat4, Vec3};
use crate::prelude::{GlobalTransform, Transform};
/// A trait for point transformation methods.
pub trait TransformPoint {
/// Transform a point.
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3;
}
impl TransformPoint for Transform {
#[inline]
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3 {
self.transform_point(point.into())
}
}
impl TransformPoint for GlobalTransform {
#[inline]
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3 {
self.transform_point(point.into())
}
}
impl TransformPoint for Mat4 {
#[inline]
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3 {
self.transform_point3(point.into())
}
}
impl TransformPoint for Affine3A {
#[inline]
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3 {
self.transform_point3(point.into())
}
}
impl TransformPoint for Isometry3d {
#[inline]
fn transform_point(&self, point: impl Into<Vec3>) -> Vec3 {
self.transform_point(point.into()).into()
}
}