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

View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"87ec76867e786ad2ac76f05c50cabf4917bf8ad5355dfc7bbee69899a4b9a8f5","Cargo.toml":"6bbd54cb2354fad8bc17bdbf688252c1b4493e76d28523cf90cc9a51f2e2d666","LICENSE-APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE-MIT":"508a77d2e7b51d98adeed32648ad124b7b30241a8e70b2e72c99f92d8e5874d1","README.md":"e1cf8e97340da3a872183002a7238520517d417037871e2b72939c71afad05d3","src/commands.rs":"426b242894be0456665b386d2b73f970e6749c3cc841d26e421232b5ba0bab39","src/components/global_transform.rs":"3ad7fe0c3160018ecbeb055c7c940d31905887cd675a11fe9245c04423dc5ce1","src/components/mod.rs":"980de4b372178111a92f2c3b71897c576141db23662e80907d63fe281a881e41","src/components/transform.rs":"da6b7bc25c64bf84f87236cb159776682e0bd551f8f785f29a81166b518fd6ae","src/helper.rs":"0cb2bc6ac202559fd498c7e6fcab3281e7eb44fe98b113041537a9b0c44e39ae","src/lib.rs":"c685275182dd6612cadfd05f253b32673834fd925ee511557b8d27d0f968950a","src/plugins.rs":"690676fcdc039e4e3821cced692359c00359c6252ce0f73d8c69cc7a602475c2","src/systems.rs":"d94d78b9518134d027df13b01d0fccffc078a19b8966505f17161c7a122cd356","src/traits.rs":"549e7de08af2e2cde06d02776dfdd42ad89a88b4110dae4039a72a229dbabe54"},"package":"df218e440bb9a19058e1b80a68a031c887bcf7bd3a145b55f361359a2fa3100d"}

1607
vendor/bevy_transform/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

181
vendor/bevy_transform/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,181 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "bevy_transform"
version = "0.16.1"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Provides transform functionality for Bevy Engine"
homepage = "https://bevyengine.org"
readme = "README.md"
keywords = ["bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"-Zunstable-options",
"--generate-link-to-definition",
]
[features]
alloc = ["serde?/alloc"]
async_executor = [
"std",
"bevy_tasks/async_executor",
]
bevy-support = [
"alloc",
"dep:bevy_app",
"dep:bevy_ecs",
]
bevy_reflect = [
"bevy-support",
"dep:bevy_reflect",
"bevy_math/bevy_reflect",
"bevy_ecs/bevy_reflect",
"bevy_app/bevy_reflect",
]
critical-section = [
"bevy_app?/critical-section",
"bevy_ecs?/critical-section",
"bevy_tasks/critical-section",
"bevy_reflect?/critical-section",
]
default = [
"std",
"bevy-support",
"bevy_reflect",
"async_executor",
]
libm = ["bevy_math/libm"]
serialize = [
"dep:serde",
"bevy_math/serialize",
]
std = [
"alloc",
"bevy_app?/std",
"bevy_log",
"bevy_ecs?/std",
"bevy_math/std",
"bevy_reflect?/std",
"bevy_tasks/std",
"bevy_utils/std",
"serde?/std",
]
[lib]
name = "bevy_transform"
path = "src/lib.rs"
[dependencies.bevy_app]
version = "0.16.1"
optional = true
default-features = false
[dependencies.bevy_ecs]
version = "0.16.1"
optional = true
default-features = false
[dependencies.bevy_log]
version = "0.16.1"
optional = true
default-features = false
[dependencies.bevy_math]
version = "0.16.1"
default-features = false
[dependencies.bevy_reflect]
version = "0.16.1"
optional = true
default-features = false
[dependencies.bevy_tasks]
version = "0.16.1"
default-features = false
[dependencies.bevy_utils]
version = "0.16.1"
optional = true
default-features = false
[dependencies.derive_more]
version = "1"
features = ["from"]
default-features = false
[dependencies.serde]
version = "1"
features = ["derive"]
optional = true
default-features = false
[dependencies.thiserror]
version = "2"
default-features = false
[dev-dependencies.approx]
version = "0.5.1"
[dev-dependencies.bevy_math]
version = "0.16.1"
features = ["approx"]
default-features = false
[dev-dependencies.bevy_tasks]
version = "0.16.1"
[lints.clippy]
alloc_instead_of_core = "warn"
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
doc_markdown = "warn"
manual_let_else = "warn"
match_same_arms = "warn"
needless_lifetimes = "allow"
nonstandard_macro_braces = "warn"
print_stderr = "warn"
print_stdout = "warn"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
redundant_closure_for_method_calls = "warn"
redundant_else = "warn"
ref_as_ptr = "warn"
semicolon_if_nothing_returned = "warn"
std_instead_of_alloc = "warn"
std_instead_of_core = "warn"
too_long_first_doc_paragraph = "allow"
too_many_arguments = "allow"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
[lints.rust]
missing_docs = "warn"
unsafe_code = "deny"
unsafe_op_in_unsafe_fn = "warn"
unused_qualifications = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = ["cfg(docsrs_dep)"]

176
vendor/bevy_transform/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

19
vendor/bevy_transform/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

9
vendor/bevy_transform/README.md vendored Normal file
View File

@@ -0,0 +1,9 @@
# Bevy Transform
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/v/bevy.svg)](https://crates.io/crates/bevy_transform)
[![Downloads](https://img.shields.io/crates/d/bevy_transform.svg)](https://crates.io/crates/bevy_transform)
[![Docs](https://docs.rs/bevy_transform/badge.svg)](https://docs.rs/bevy_transform/latest/bevy_transform/)
[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy)
This crate contains types and functions associated with the `Transform` component.

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()
}
}