From 41f5cc42c38ecef991049e8ce52c24b9c1d3d86c Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 11 Mar 2025 12:45:25 -0300 Subject: [PATCH 01/10] Make `AnimationTargetId` into `Component` and add `include_animation_target_ids` to `GltfLoaderSettings` --- crates/bevy_animation/src/lib.rs | 5 +- crates/bevy_gltf/src/loader/mod.rs | 76 ++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 8663ea3f3f970..be8a9bf7ba093 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -171,7 +171,10 @@ pub type AnimationCurves = HashMap, NoOpHa /// connected to a bone named `Stomach`. /// /// [UUID]: https://en.wikipedia.org/wiki/Universally_unique_identifier -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Reflect, Debug, Serialize, Deserialize)] +#[derive( + Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Component, Reflect, Debug, Serialize, Deserialize, +)] +#[reflect(Component)] pub struct AnimationTargetId(pub Uuid); impl Hash for AnimationTargetId { diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index 9d400e44bc0cb..ccb2c715b2ea2 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -17,7 +17,7 @@ use bevy_ecs::{ entity::{hash_map::EntityHashMap, Entity}, hierarchy::ChildSpawner, name::Name, - world::World, + world::{EntityWorldMut, World}, }; use bevy_image::{ CompressedImageFormats, Image, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor, @@ -176,6 +176,9 @@ pub struct GltfLoaderSettings { pub load_cameras: bool, /// If true, the loader will spawn lights for gltf light nodes. pub load_lights: bool, + /// If true, the loader will include [`AnimationTargetId`] for all nodes, + /// even if there is no animation on the Gltf + pub include_animation_target_ids: bool, /// If true, the loader will include the root of the gltf root node. pub include_source: bool, } @@ -187,6 +190,7 @@ impl Default for GltfLoaderSettings { load_materials: RenderAssetUsages::default(), load_cameras: true, load_lights: true, + include_animation_target_ids: false, include_source: false, } } @@ -1312,22 +1316,25 @@ fn load_node( node.insert(name.clone()); #[cfg(feature = "bevy_animation")] - if animation_context.is_none() && animation_roots.contains(&gltf_node.index()) { - // This is an animation root. Make a new animation context. - animation_context = Some(AnimationContext { - root: node.id(), - path: SmallVec::new(), - }); + if animation_context.is_none() { + if animation_roots.contains(&gltf_node.index()) { + // This is an animation root. Make a new animation context. + animation_context = Some(AnimationContext::Animation { + root: node.id(), + path: SmallVec::new(), + }); + } else if settings.include_animation_target_ids { + // Creating a AnimationContext to collect the paths for the `AnimationTargetId`. + animation_context = Some(AnimationContext::JustTargetId { + path: SmallVec::new(), + }); + } } #[cfg(feature = "bevy_animation")] if let Some(ref mut animation_context) = animation_context { - animation_context.path.push(name); - - node.insert(AnimationTarget { - id: AnimationTargetId::from_names(animation_context.path.iter()), - player: animation_context.root, - }); + animation_context.push_name(name); + animation_context.insert_to_entity(&mut node); } if let Some(extras) = gltf_node.extras() { @@ -1740,12 +1747,43 @@ impl<'s> Iterator for PrimitiveMorphAttributesIter<'s> { /// nearest ancestor animation root. #[cfg(feature = "bevy_animation")] #[derive(Clone)] -struct AnimationContext { - /// The nearest ancestor animation root. - pub root: Entity, - /// The path to the animation root. This is used for constructing the - /// animation target UUIDs. - pub path: SmallVec<[Name; 8]>, +enum AnimationContext { + Animation { + /// The nearest ancestor animation root. + root: Entity, + /// The path to the animation root. This is used for constructing the + /// animation target UUIDs. + path: SmallVec<[Name; 8]>, + }, + JustTargetId { + /// The path to the animation root. This is used for constructing the + /// animation target UUIDs. + path: SmallVec<[Name; 8]>, + }, +} + +impl AnimationContext { + fn push_name(&mut self, name: Name) { + let path = match self { + Self::JustTargetId { path } | Self::Animation { root: _, path } => path, + }; + + path.push(name); + } + + fn insert_to_entity(&self, node: &mut EntityWorldMut) { + match self { + Self::Animation { root, path } => { + node.insert(AnimationTarget { + id: AnimationTargetId::from_names(path.iter()), + player: *root, + }); + } + Self::JustTargetId { path } => { + node.insert(AnimationTargetId::from_names(path.iter())); + } + } + } } #[derive(Deserialize)] From 52be52342f1b37c8e415f19766b69d306700b3ca Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 11 Mar 2025 15:13:43 -0300 Subject: [PATCH 02/10] Lock some things on `bevy_gltf` behind `cfg` --- crates/bevy_gltf/src/loader/mod.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index ccb2c715b2ea2..1ff7fdab18af9 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -13,11 +13,13 @@ use bevy_asset::{ }; use bevy_color::{Color, LinearRgba}; use bevy_core_pipeline::prelude::Camera3d; +#[cfg(feature = "bevy_animation")] +use bevy_ecs::world::EntityWorldMut; use bevy_ecs::{ entity::{hash_map::EntityHashMap, Entity}, hierarchy::ChildSpawner, name::Name, - world::{EntityWorldMut, World}, + world::World, }; use bevy_image::{ CompressedImageFormats, Image, ImageLoaderSettings, ImageSampler, ImageSamplerDescriptor, @@ -66,6 +68,8 @@ use crate::{ GltfMaterialName, GltfMeshExtras, GltfNode, GltfSceneExtras, GltfSkin, }; +#[cfg(feature = "bevy_animation")] +use self::gltf_ext::scene::collect_path; use self::{ extensions::{AnisotropyExtension, ClearcoatExtension, SpecularExtension}, gltf_ext::{ @@ -75,7 +79,7 @@ use self::{ warn_on_differing_texture_transforms, }, mesh::{primitive_name, primitive_topology}, - scene::{collect_path, node_name, node_transform}, + scene::{node_name, node_transform}, texture::{texture_handle, texture_sampler, texture_transform_to_affine2}, }, }; @@ -1762,6 +1766,7 @@ enum AnimationContext { }, } +#[cfg(feature = "bevy_animation")] impl AnimationContext { fn push_name(&mut self, name: Name) { let path = match self { From 6fea2b5252e4b8443052e5d78c20cbbccbae7a68 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 11 Mar 2025 16:13:17 -0300 Subject: [PATCH 03/10] Bone attachments --- Cargo.toml | 25 +++ assets/models/animated/FoxHelm.glb | Bin 0 -> 13672 bytes crates/bevy_bone_attachments/Cargo.toml | 32 ++++ crates/bevy_bone_attachments/LICENSE-APACHE | 176 ++++++++++++++++++ crates/bevy_bone_attachments/LICENSE-MIT | 19 ++ crates/bevy_bone_attachments/README.md | 19 ++ crates/bevy_bone_attachments/src/lib.rs | 30 +++ .../bevy_bone_attachments/src/relationship.rs | 23 +++ crates/bevy_bone_attachments/src/scene.rs | 115 ++++++++++++ crates/bevy_internal/Cargo.toml | 5 + crates/bevy_internal/src/default_plugins.rs | 2 + crates/bevy_internal/src/lib.rs | 2 + crates/bevy_internal/src/prelude.rs | 4 + examples/README.md | 1 + examples/animation/bone_attachments.rs | 171 +++++++++++++++++ 15 files changed, 624 insertions(+) create mode 100644 assets/models/animated/FoxHelm.glb create mode 100644 crates/bevy_bone_attachments/Cargo.toml create mode 100644 crates/bevy_bone_attachments/LICENSE-APACHE create mode 100644 crates/bevy_bone_attachments/LICENSE-MIT create mode 100644 crates/bevy_bone_attachments/README.md create mode 100644 crates/bevy_bone_attachments/src/lib.rs create mode 100644 crates/bevy_bone_attachments/src/relationship.rs create mode 100644 crates/bevy_bone_attachments/src/scene.rs create mode 100644 examples/animation/bone_attachments.rs diff --git a/Cargo.toml b/Cargo.toml index e9e593b6079f2..eefc4bd4b5058 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -193,6 +193,9 @@ sysinfo_plugin = ["bevy_internal/sysinfo_plugin"] # Provides animation functionality bevy_animation = ["bevy_internal/bevy_animation", "bevy_color"] +# Provides animation functionality +bevy_bone_attachments = ["bevy_internal/bevy_bone_attachments"] + # Provides asset functionality bevy_asset = ["bevy_internal/bevy_asset"] @@ -1437,6 +1440,28 @@ description = "Plays an animation from a skinned glTF with events" category = "Animation" wasm = true +[[example]] +name = "bone_attachments" +path = "examples/animation/bone_attachments.rs" +doc-scrape-examples = true +required-features = [ + "std", + "animation", + "bevy_bone_attachments", + "bevy_gltf", + "bevy_window", + # Application crashes on exit if disabled + "multi_threaded", + "png", + "tonemapping_luts", +] + +[package.metadata.example.bone_attachments] +name = "Bone Attachments" +description = "Shows attachting a model to another" +category = "Animation" +wasm = true + [[example]] name = "animation_graph" path = "examples/animation/animation_graph.rs" diff --git a/assets/models/animated/FoxHelm.glb b/assets/models/animated/FoxHelm.glb new file mode 100644 index 0000000000000000000000000000000000000000..99ded1708869b8cd41b1d852a091628ab4382d80 GIT binary patch literal 13672 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 BBIN)8 literal 0 HcmV?d00001 diff --git a/crates/bevy_bone_attachments/Cargo.toml b/crates/bevy_bone_attachments/Cargo.toml new file mode 100644 index 0000000000000..dd82309a7152b --- /dev/null +++ b/crates/bevy_bone_attachments/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "bevy_bone_attachments" +version = "0.16.0-dev" +edition = "2024" +description = "Experimental implementation of bone attachments" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy", "animation"] +rust-version = "1.85.0" + +[dependencies] +bevy_animation = { path = "../bevy_animation", version = "0.16.0-dev", default-features = false } +bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false } +bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev", default-features = false, optional = true } +bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev", default-features = false } +bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false } +bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false } +bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false } +bevy_scene = { path = "../bevy_scene", version = "0.16.0-dev", default-features = false, optional = true } + +tracing = { version = "0.1", default-features = false } + +[features] +bevy_scene = ["dep:bevy_scene", "dep:bevy_asset"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_bone_attachments/LICENSE-APACHE b/crates/bevy_bone_attachments/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_bone_attachments/LICENSE-APACHE @@ -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 diff --git a/crates/bevy_bone_attachments/LICENSE-MIT b/crates/bevy_bone_attachments/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_bone_attachments/LICENSE-MIT @@ -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. diff --git a/crates/bevy_bone_attachments/README.md b/crates/bevy_bone_attachments/README.md new file mode 100644 index 0000000000000..56c100ff46ac7 --- /dev/null +++ b/crates/bevy_bone_attachments/README.md @@ -0,0 +1,19 @@ +# Bevy Bone Attachments + +Bone attachments are used to accessorize a model with others. A common use is for example +to attach a weapon to a characters hands. + +This relies on the parent model having `AnimationTarget` components and the attachment having +`AnimationTargetId` component. + +Currently this only works by attaching a `Scene` to another entity. If the `Scene` is loaded +from a `glTf`, use the `GltfLoaderSetting::include_animation_target_ids` setting to load the `AnimationTargetId` +of the attachment. + +# Links + +[![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_color.svg)](https://crates.io/crates/bevy_color) +[![Downloads](https://img.shields.io/crates/d/bevy_color.svg)](https://crates.io/crates/bevy_color) +[![Docs](https://docs.rs/bevy_color/badge.svg)](https://docs.rs/bevy_color/latest/bevy_color/) +[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) \ No newline at end of file diff --git a/crates/bevy_bone_attachments/src/lib.rs b/crates/bevy_bone_attachments/src/lib.rs new file mode 100644 index 0000000000000..f538c45553d6a --- /dev/null +++ b/crates/bevy_bone_attachments/src/lib.rs @@ -0,0 +1,30 @@ +//! Bone attachments are used to connect a model to another + +#![no_std] + +pub mod relationship; +#[cfg(feature = "bevy_scene")] +pub mod scene; + +extern crate alloc; + +use bevy_app::Plugin; + +/// Most frequently used objects of [`bevy_bone_attachments`](self) for easy access +pub mod prelude { + pub use super::{ + relationship::{AttachedTo, AttachingModels}, + BoneAttachmentsPlugin, + }; +} + +#[derive(Default)] +/// Plugin that setups bone attachments +pub struct BoneAttachmentsPlugin; + +impl Plugin for BoneAttachmentsPlugin { + fn build(&self, app: &mut bevy_app::App) { + app.register_type::() + .register_type::(); + } +} diff --git a/crates/bevy_bone_attachments/src/relationship.rs b/crates/bevy_bone_attachments/src/relationship.rs new file mode 100644 index 0000000000000..59e6970f7f4a5 --- /dev/null +++ b/crates/bevy_bone_attachments/src/relationship.rs @@ -0,0 +1,23 @@ +//! Define the relationship between attaching and attached models + +use alloc::vec::Vec; + +use bevy_derive::Deref; +use bevy_ecs::{component::Component, entity::Entity}; +use bevy_reflect::Reflect; + +#[derive(Debug, Component, Reflect)] +#[relationship_target(relationship = AttachedTo)] +/// List of models attached to this model +pub struct AttachingModels(Vec); + +#[derive(Debug, Component, Reflect, Deref)] +#[relationship(relationship_target=AttachingModels)] +/// Model this entity is attached to +pub struct AttachedTo(Entity); + +impl From for AttachedTo { + fn from(entity: Entity) -> Self { + Self(entity) + } +} diff --git a/crates/bevy_bone_attachments/src/scene.rs b/crates/bevy_bone_attachments/src/scene.rs new file mode 100644 index 0000000000000..1b64b5233b198 --- /dev/null +++ b/crates/bevy_bone_attachments/src/scene.rs @@ -0,0 +1,115 @@ +//! Types to help attaching a scene to an entity + +use alloc::vec::Vec; +use bevy_animation::{AnimationTarget, AnimationTargetId}; +use bevy_asset::Handle; +use bevy_ecs::{ + bundle::Bundle, + hierarchy::Children, + observer::Trigger, + relationship::RelatedSpawnerCommands, + system::{Commands, EntityCommands, Query}, +}; +use bevy_platform_support::collections::{hash_map::Entry, HashMap}; +use bevy_scene::{Scene, SceneInstanceReady, SceneRoot}; + +use crate::prelude::AttachedTo; + +/// Extension trait for [`EntityCommands`] to allow attaching a [`Scene`] to an [`Entity`](bevy_ecs::entity::Entity). +pub trait SceneAttachmentExt { + /// Attaches a [`Scene`] to an [`Entity`](bevy_ecs::entity::Entity). + fn attach_scene(&mut self, scene: Handle) -> &mut Self; + + /// Attaches a [`Scene`] to an [`Entity`](bevy_ecs::entity::Entity) and inserts an extra [`Bundle`] + /// on the attachment. + fn attach_scene_with_extras(&mut self, scene: Handle, extras: impl Bundle) -> &mut Self; +} + +impl<'a> SceneAttachmentExt for EntityCommands<'a> { + #[inline] + fn attach_scene(&mut self, scene: Handle) -> &mut EntityCommands<'a> { + self.attach_scene_with_extras(scene, ()) + } + + #[inline] + fn attach_scene_with_extras( + &mut self, + scene: Handle, + extras: impl Bundle, + ) -> &mut EntityCommands<'a> { + self.with_related(|spawner: &mut RelatedSpawnerCommands| { + spawner + .spawn((SceneRoot(scene), extras)) + .observe(scene_attachment_ready); + }) + } +} + +fn scene_attachment_ready( + trigger: Trigger, + mut commands: Commands, + scene_attachments: Query<&AttachedTo>, + children: Query<&Children>, + animation_targets: Query<&AnimationTarget>, + animation_target_ids: Query<&AnimationTargetId>, +) { + let Ok(parent) = scene_attachments.get(trigger.target()) else { + unreachable!("AttachedTo must be available on SceneInstanceReady."); + }; + + let mut duplicate_target_ids_on_parent_hierarchy = Vec::new(); + let mut target_ids = HashMap::new(); + for child in children.iter_descendants(**parent) { + if child == trigger.target() { + continue; + } + + if let Ok(animation_target) = animation_targets.get(child) { + match target_ids.entry(animation_target.id) { + Entry::Vacant(vacancy) => { + vacancy.insert(animation_target.player); + } + Entry::Occupied(_) => { + duplicate_target_ids_on_parent_hierarchy.push(animation_target.id); + } + } + } + } + if !duplicate_target_ids_on_parent_hierarchy.is_empty() { + tracing::warn!( + "There where nodes with duplicate AnimationTargetId on the hierarchy if {}, using the first appearance. {:?}", + **parent, + duplicate_target_ids_on_parent_hierarchy + ); + } + + let mut count = 0; + let mut unmatched_animation_target_id = Vec::new(); + for child in children.iter_descendants(trigger.target()) { + if let Ok(animation_target_id) = animation_target_ids.get(child) { + if let Some(player) = target_ids.get(animation_target_id) { + commands.entity(child).insert(AnimationTarget { + id: *animation_target_id, + player: *player, + }); + count += 1; + } else { + unmatched_animation_target_id.push(animation_target_id); + } + } + } + if !unmatched_animation_target_id.is_empty() { + tracing::warn!( + "There where nodes with unmatched AnimationTargetId on the hierarchy if {}, this may cause bone attachment to not update correctly. {:?}", + trigger.target(), + unmatched_animation_target_id + ); + } + tracing::debug!( + "Attachment {} matched {} nodes with parent.", + trigger.target(), + count + ); + + commands.entity(trigger.target()); +} diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 073de918bd955..90e5c7915fa36 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -172,6 +172,9 @@ bevy_ci_testing = ["bevy_dev_tools/bevy_ci_testing", "bevy_render?/ci_limits"] # Enable animation support, and glTF animation loading animation = ["bevy_animation", "bevy_gltf?/bevy_animation"] +# Enables bone attachments +bevy_bone_attachments = ["dep:bevy_bone_attachments", "bevy_animation"] + bevy_sprite = ["dep:bevy_sprite", "bevy_gizmos", "bevy_image"] bevy_pbr = ["dep:bevy_pbr", "bevy_gizmos?/bevy_pbr", "bevy_image"] bevy_window = ["dep:bevy_window", "dep:bevy_a11y"] @@ -201,6 +204,7 @@ bevy_render = [ "bevy_color/wgpu-types", "bevy_color/encase", ] +bevy_scene = ["dep:bevy_scene", "bevy_bone_attachments?/bevy_scene"] # Enable assertions to check the validity of parameters passed to glam glam_assert = ["bevy_math/glam_assert"] @@ -353,6 +357,7 @@ web = [ bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [ "bevy_reflect", ] } +bevy_bone_attachments = { path = "../bevy_bone_attachments", version = "0.16.0-dev", default-features = false, optional = true } bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev", default-features = false } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false, features = [ diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 259a8c89226cd..0af7a408bfd81 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -58,6 +58,8 @@ plugin_group! { bevy_gilrs:::GilrsPlugin, #[cfg(feature = "bevy_animation")] bevy_animation:::AnimationPlugin, + #[cfg(feature = "bevy_bone_attachments")] + bevy_bone_attachments:::BoneAttachmentsPlugin, #[cfg(feature = "bevy_gizmos")] bevy_gizmos:::GizmoPlugin, #[cfg(feature = "bevy_state")] diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 37bcef26b2ad7..817b799eea0c8 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -23,6 +23,8 @@ pub use bevy_app as app; pub use bevy_asset as asset; #[cfg(feature = "bevy_audio")] pub use bevy_audio as audio; +#[cfg(feature = "bevy_bone_attachments")] +pub use bevy_bone_attachments as bone_attachments; #[cfg(feature = "bevy_color")] pub use bevy_color as color; #[cfg(feature = "bevy_core_pipeline")] diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index 9f7dd8f33e267..bd72f61a7b134 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -34,6 +34,10 @@ pub use crate::audio::prelude::*; #[cfg(feature = "bevy_animation")] pub use crate::animation::prelude::*; +#[doc(hidden)] +#[cfg(feature = "bevy_bone_attachments")] +pub use crate::bone_attachments::prelude::*; + #[doc(hidden)] #[cfg(feature = "bevy_color")] pub use crate::color::prelude::*; diff --git a/examples/README.md b/examples/README.md index 1cf4d14070206..137cf71258f1f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -209,6 +209,7 @@ Example | Description [Animation Events](../examples/animation/animation_events.rs) | Demonstrate how to use animation events [Animation Graph](../examples/animation/animation_graph.rs) | Blends multiple animations together with a graph [Animation Masks](../examples/animation/animation_masks.rs) | Demonstrates animation masks +[Bone Attachments](../examples/animation/bone_attachments.rs) | Shows attachting a model to another [Color animation](../examples/animation/color_animation.rs) | Demonstrates how to animate colors using mixing and splines in different color spaces [Custom Skinned Mesh](../examples/animation/custom_skinned_mesh.rs) | Skinned mesh example with mesh and joints data defined in code [Eased Motion](../examples/animation/eased_motion.rs) | Demonstrates the application of easing curves to animate an object diff --git a/examples/animation/bone_attachments.rs b/examples/animation/bone_attachments.rs new file mode 100644 index 0000000000000..2329cd724b10d --- /dev/null +++ b/examples/animation/bone_attachments.rs @@ -0,0 +1,171 @@ +//! Shows attachting a model to another + +use std::f32::consts::PI; + +use bevy::{ + app::{App, Startup}, + bone_attachments::scene::SceneAttachmentExt, + color::Color, + gltf::{GltfAssetLabel, GltfLoaderSettings}, + math::{EulerRot, Quat, Vec3}, + pbr::{ + AmbientLight, CascadeShadowConfigBuilder, DirectionalLight, MeshMaterial3d, + StandardMaterial, + }, + prelude::{ + AnimationGraph, AnimationGraphHandle, AnimationNodeIndex, AnimationPlayer, Camera3d, + Children, Commands, Component, Mesh, Mesh3d, Meshable, Plane3d, Query, Res, ResMut, + Transform, Trigger, + }, + scene::{SceneInstanceReady, SceneRoot}, + utils::default, + DefaultPlugins, +}; +use bevy_asset::{AssetServer, Assets, Handle}; + +// Example asset that contains a mesh, animation, and attachment. +const GLTF_PATH: &str = "models/animated/Fox.glb"; +const ATTACHMENT_PATH: &str = "models/animated/FoxHelm.glb"; + +fn main() { + App::new() + .insert_resource(AmbientLight { + color: Color::WHITE, + brightness: 2000., + ..default() + }) + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup_mesh_and_animation) + .add_systems(Startup, setup_camera_and_environment) + .run(); +} + +// A component that stores a reference to an animation we want to play. This is +// created when we start loading the mesh (see `setup_mesh_and_animation`) and +// read when the mesh has spawned (see `play_animation_once_loaded`). +#[derive(Component)] +struct AnimationToPlay { + graph_handle: Handle, + index: AnimationNodeIndex, +} + +fn setup_mesh_and_animation( + mut commands: Commands, + asset_server: Res, + mut graphs: ResMut>, +) { + // Create an animation graph containing a single animation. We want the "run" + // animation from our example asset, which has an index of two. + let (graph, index) = AnimationGraph::from_clip( + asset_server.load(GltfAssetLabel::Animation(2).from_asset(GLTF_PATH)), + ); + + // Store the animation graph as an asset. + let graph_handle = graphs.add(graph); + + // Create a component that stores a reference to our animation. + let animation_to_play = AnimationToPlay { + graph_handle, + index, + }; + + // Start loading the assets as a scene and store a reference to it in a + // SceneRoot component. This component will automatically spawn a scene + // containing our mesh once it has loaded. + let mesh_scene = SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH))); + + // Spawn an entity with our components, and connect it to an observer that + // will trigger when the scene is loaded and spawned. + commands + .spawn((animation_to_play, mesh_scene)) + .observe(play_animation_when_ready) + .observe(attach_helm); +} + +fn play_animation_when_ready( + trigger: Trigger, + mut commands: Commands, + children: Query<&Children>, + animations_to_play: Query<&AnimationToPlay>, + mut players: Query<&mut AnimationPlayer>, +) { + // The entity we spawned in `setup_mesh_and_animation` is the trigger's target. + // Start by finding the AnimationToPlay component we added to that entity. + if let Ok(animation_to_play) = animations_to_play.get(trigger.target()) { + // The SceneRoot component will have spawned the scene as a hierarchy + // of entities parented to our entity. Since the asset contained a skinned + // mesh and animations, it will also have spawned an animation player + // component. Search our entity's descendants to find the animation player. + for child in children.iter_descendants(trigger.target()) { + if let Ok(mut player) = players.get_mut(child) { + // Tell the animation player to start the animation and keep + // repeating it. + // + // If you want to try stopping and switching animations, see the + // `animated_mesh_control.rs` example. + player.play(animation_to_play.index).repeat(); + + // Add the animation graph. This only needs to be done once to + // connect the animation player to the mesh. + commands + .entity(child) + .insert(AnimationGraphHandle(animation_to_play.graph_handle.clone())); + } + } + } +} + +/// Attaches a helm to the fox, this needs to wait for the scene of the fox to finish loading to be +/// able to query for the [`AnimationTarget`](bevy::animation::AnimationTarget)s +fn attach_helm( + trigger: Trigger, + mut commands: Commands, + asset_server: Res, +) { + // Start loading if the attachment. + let attachment_scene = asset_server.load_with_settings( + GltfAssetLabel::Scene(0).from_asset(ATTACHMENT_PATH), + |settings: &mut GltfLoaderSettings| { + settings.include_animation_target_ids = true; + }, + ); + + // Spawns an entity attached to the entity above + commands + .entity(trigger.target()) + .attach_scene(attachment_scene); +} + +// Spawn a camera and a simple environment with a ground plane and light. +fn setup_camera_and_environment( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // Camera + commands.spawn(( + Camera3d::default(), + Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y), + )); + + // Plane + commands.spawn(( + Mesh3d(meshes.add(Plane3d::default().mesh().size(500000.0, 500000.0))), + MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))), + )); + + // Light + commands.spawn(( + Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)), + DirectionalLight { + shadows_enabled: true, + ..default() + }, + CascadeShadowConfigBuilder { + first_cascade_far_bound: 200.0, + maximum_distance: 400.0, + ..default() + } + .build(), + )); +} From 2683afece10c71c7c78a7310d95fa52aeb82f2ce Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 11 Mar 2025 16:28:09 -0300 Subject: [PATCH 04/10] Simplify import on `bone_attachments` example --- examples/animation/bone_attachments.rs | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/examples/animation/bone_attachments.rs b/examples/animation/bone_attachments.rs index 2329cd724b10d..7a8ba6ea61d90 100644 --- a/examples/animation/bone_attachments.rs +++ b/examples/animation/bone_attachments.rs @@ -3,25 +3,9 @@ use std::f32::consts::PI; use bevy::{ - app::{App, Startup}, - bone_attachments::scene::SceneAttachmentExt, - color::Color, - gltf::{GltfAssetLabel, GltfLoaderSettings}, - math::{EulerRot, Quat, Vec3}, - pbr::{ - AmbientLight, CascadeShadowConfigBuilder, DirectionalLight, MeshMaterial3d, - StandardMaterial, - }, - prelude::{ - AnimationGraph, AnimationGraphHandle, AnimationNodeIndex, AnimationPlayer, Camera3d, - Children, Commands, Component, Mesh, Mesh3d, Meshable, Plane3d, Query, Res, ResMut, - Transform, Trigger, - }, - scene::{SceneInstanceReady, SceneRoot}, - utils::default, - DefaultPlugins, + bone_attachments::scene::SceneAttachmentExt, gltf::GltfLoaderSettings, + pbr::CascadeShadowConfigBuilder, prelude::*, scene::SceneInstanceReady, }; -use bevy_asset::{AssetServer, Assets, Handle}; // Example asset that contains a mesh, animation, and attachment. const GLTF_PATH: &str = "models/animated/Fox.glb"; From a8c2eb6e76ebac4bea8f8188287ad84435c22b0e Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 11 Mar 2025 16:29:53 -0300 Subject: [PATCH 05/10] Run update features `build-templated-pages` --- Cargo.toml | 2 +- docs/cargo_features.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index eefc4bd4b5058..e7e6df8b10251 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -193,7 +193,7 @@ sysinfo_plugin = ["bevy_internal/sysinfo_plugin"] # Provides animation functionality bevy_animation = ["bevy_internal/bevy_animation", "bevy_color"] -# Provides animation functionality +# Provides methods to attach an entity to another bevy_bone_attachments = ["bevy_internal/bevy_bone_attachments"] # Provides asset functionality diff --git a/docs/cargo_features.md b/docs/cargo_features.md index a96c2697f5ddd..7a2906286f06d 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -62,6 +62,7 @@ The default feature set enables most of the expected features of a game engine, |asset_processor|Enables the built-in asset processor for processed assets.| |async-io|Use async-io's implementation of block_on instead of futures-lite's implementation. This is preferred if your application uses async-io.| |basis-universal|Basis Universal compressed texture support| +|bevy_bone_attachments|Provides methods to attach an entity to another| |bevy_ci_testing|Enable systems that allow for automated testing on CI| |bevy_debug_stepping|Enable stepping-based debugging of Bevy systems| |bevy_dev_tools|Provides a collection of developer tools| From c8592c4c3d36ff0c78174d3fadf109cf7e79eb96 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 11 Mar 2025 16:31:06 -0300 Subject: [PATCH 06/10] Markdown lints on `bevy_bone_attachments` README --- crates/bevy_bone_attachments/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_bone_attachments/README.md b/crates/bevy_bone_attachments/README.md index 56c100ff46ac7..40d16f765dd96 100644 --- a/crates/bevy_bone_attachments/README.md +++ b/crates/bevy_bone_attachments/README.md @@ -4,16 +4,16 @@ Bone attachments are used to accessorize a model with others. A common use is fo to attach a weapon to a characters hands. This relies on the parent model having `AnimationTarget` components and the attachment having -`AnimationTargetId` component. +`AnimationTargetId` component. Currently this only works by attaching a `Scene` to another entity. If the `Scene` is loaded from a `glTf`, use the `GltfLoaderSetting::include_animation_target_ids` setting to load the `AnimationTargetId` of the attachment. -# Links +## Links [![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_color.svg)](https://crates.io/crates/bevy_color) [![Downloads](https://img.shields.io/crates/d/bevy_color.svg)](https://crates.io/crates/bevy_color) [![Docs](https://docs.rs/bevy_color/badge.svg)](https://docs.rs/bevy_color/latest/bevy_color/) -[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) \ No newline at end of file +[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy) From 8490053c90bb7d27d0756212d28a34b94c0df78f Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Tue, 11 Mar 2025 19:12:06 -0300 Subject: [PATCH 07/10] Fix typo --- Cargo.toml | 2 +- examples/README.md | 2 +- examples/animation/bone_attachments.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e7e6df8b10251..9693c2ee54588 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1458,7 +1458,7 @@ required-features = [ [package.metadata.example.bone_attachments] name = "Bone Attachments" -description = "Shows attachting a model to another" +description = "Shows attaching a model to another" category = "Animation" wasm = true diff --git a/examples/README.md b/examples/README.md index 137cf71258f1f..b8e6c89084859 100644 --- a/examples/README.md +++ b/examples/README.md @@ -209,7 +209,7 @@ Example | Description [Animation Events](../examples/animation/animation_events.rs) | Demonstrate how to use animation events [Animation Graph](../examples/animation/animation_graph.rs) | Blends multiple animations together with a graph [Animation Masks](../examples/animation/animation_masks.rs) | Demonstrates animation masks -[Bone Attachments](../examples/animation/bone_attachments.rs) | Shows attachting a model to another +[Bone Attachments](../examples/animation/bone_attachments.rs) | Shows attaching a model to another [Color animation](../examples/animation/color_animation.rs) | Demonstrates how to animate colors using mixing and splines in different color spaces [Custom Skinned Mesh](../examples/animation/custom_skinned_mesh.rs) | Skinned mesh example with mesh and joints data defined in code [Eased Motion](../examples/animation/eased_motion.rs) | Demonstrates the application of easing curves to animate an object diff --git a/examples/animation/bone_attachments.rs b/examples/animation/bone_attachments.rs index 7a8ba6ea61d90..82614b34db570 100644 --- a/examples/animation/bone_attachments.rs +++ b/examples/animation/bone_attachments.rs @@ -1,4 +1,4 @@ -//! Shows attachting a model to another +//! Shows attaching a model to another use std::f32::consts::PI; From 701afdbf19c8412ec557fd71236e7c6bae0c59f7 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Wed, 12 Mar 2025 07:32:36 -0300 Subject: [PATCH 08/10] Propagate parents' transforms to attachments --- crates/bevy_bone_attachments/Cargo.toml | 1 + crates/bevy_bone_attachments/src/lib.rs | 35 ++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/crates/bevy_bone_attachments/Cargo.toml b/crates/bevy_bone_attachments/Cargo.toml index dd82309a7152b..3a6bb5e1b72c3 100644 --- a/crates/bevy_bone_attachments/Cargo.toml +++ b/crates/bevy_bone_attachments/Cargo.toml @@ -18,6 +18,7 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = fa bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false } bevy_scene = { path = "../bevy_scene", version = "0.16.0-dev", default-features = false, optional = true } +bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev", default-features = false } tracing = { version = "0.1", default-features = false } diff --git a/crates/bevy_bone_attachments/src/lib.rs b/crates/bevy_bone_attachments/src/lib.rs index f538c45553d6a..3466fb3c59723 100644 --- a/crates/bevy_bone_attachments/src/lib.rs +++ b/crates/bevy_bone_attachments/src/lib.rs @@ -8,7 +8,12 @@ pub mod scene; extern crate alloc; -use bevy_app::Plugin; +use alloc::vec::Vec; +use bevy_app::{Plugin, PostUpdate}; +use bevy_ecs::{ + entity::Entity, relationship::RelationshipTarget, schedule::IntoSystemConfigs, system::Query, +}; +use bevy_transform::{components::Transform, TransformSystem}; /// Most frequently used objects of [`bevy_bone_attachments`](self) for easy access pub mod prelude { @@ -26,5 +31,33 @@ impl Plugin for BoneAttachmentsPlugin { fn build(&self, app: &mut bevy_app::App) { app.register_type::() .register_type::(); + + app.add_systems( + PostUpdate, + propagate_transform_to_attachments.after(TransformSystem::TransformPropagate), + ); + } +} + +fn propagate_transform_to_attachments( + parents: Query<(Entity, &relationship::AttachingModels)>, + mut transforms: Query<&mut Transform>, +) { + let mut parents_without_transform = Vec::new(); + let mut children_without_transform = Vec::new(); + for (entity, children) in parents.iter() { + let Ok(parent_transform) = transforms.get(entity).cloned() else { + parents_without_transform.push(entity); + continue; + }; + + for child in children.iter() { + let Ok(mut transform) = transforms.get_mut(child) else { + children_without_transform.push(child); + continue; + }; + + *transform = parent_transform; + } } } From 081665b245c551ba196ba9724f51864df306ec09 Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Wed, 12 Mar 2025 08:29:47 -0300 Subject: [PATCH 09/10] Change model used for bone attachment example to better show skinned mesh --- assets/models/animated/FoxAttachment.glb | Bin 0 -> 33200 bytes assets/models/animated/FoxHelm.glb | Bin 13672 -> 0 bytes examples/animation/bone_attachments.rs | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/models/animated/FoxAttachment.glb delete mode 100644 assets/models/animated/FoxHelm.glb diff --git a/assets/models/animated/FoxAttachment.glb b/assets/models/animated/FoxAttachment.glb new file mode 100644 index 0000000000000000000000000000000000000000..54167a4fa18b7235989d7ffbe8dd7c6d8fb98893 GIT binary patch literal 33200 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 OM!1_l7r)Pi-2V@{9E?5y literal 0 HcmV?d00001 diff --git a/assets/models/animated/FoxHelm.glb b/assets/models/animated/FoxHelm.glb deleted file mode 100644 index 99ded1708869b8cd41b1d852a091628ab4382d80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13672 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 BBIN)8 diff --git a/examples/animation/bone_attachments.rs b/examples/animation/bone_attachments.rs index 82614b34db570..8b833b35efe00 100644 --- a/examples/animation/bone_attachments.rs +++ b/examples/animation/bone_attachments.rs @@ -9,7 +9,7 @@ use bevy::{ // Example asset that contains a mesh, animation, and attachment. const GLTF_PATH: &str = "models/animated/Fox.glb"; -const ATTACHMENT_PATH: &str = "models/animated/FoxHelm.glb"; +const ATTACHMENT_PATH: &str = "models/animated/FoxAttachment.glb"; fn main() { App::new() From fae0b1d9c59e00968e98b030b64ac99bbff0f79b Mon Sep 17 00:00:00 2001 From: Lucas Farias Date: Wed, 12 Mar 2025 09:39:29 -0300 Subject: [PATCH 10/10] Fix import using trait that was renamed --- crates/bevy_bone_attachments/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_bone_attachments/src/lib.rs b/crates/bevy_bone_attachments/src/lib.rs index 3466fb3c59723..7e87a4834a2ad 100644 --- a/crates/bevy_bone_attachments/src/lib.rs +++ b/crates/bevy_bone_attachments/src/lib.rs @@ -11,7 +11,7 @@ extern crate alloc; use alloc::vec::Vec; use bevy_app::{Plugin, PostUpdate}; use bevy_ecs::{ - entity::Entity, relationship::RelationshipTarget, schedule::IntoSystemConfigs, system::Query, + entity::Entity, relationship::RelationshipTarget, schedule::IntoScheduleConfigs, system::Query, }; use bevy_transform::{components::Transform, TransformSystem};