diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index a848ee47f8d2f..39b9d558132df 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -1244,9 +1244,7 @@ impl Plugin for AnimationPlugin { // it to its own system set after `Update` but before // `PostUpdate`. For now, we just disable ambiguity testing // for this system. - animate_targets - .before(bevy_mesh::InheritWeights) - .ambiguous_with_all(), + animate_targets.ambiguous_with_all(), trigger_untargeted_animation_events, expire_completed_transitions, ) diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index 50a6e42b39f0c..fa321d667459f 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -1509,7 +1509,7 @@ fn load_node( // Map node index to entity node_index_to_entity_map.insert(gltf_node.index(), node.id()); - let mut morph_weights = None; + let mut max_morph_target_count = 0; node.with_children(|parent| { // Only include meshes in the output if they're set to be retained in the MAIN_WORLD and/or RENDER_WORLD by the load_meshes flag @@ -1536,6 +1536,7 @@ fn load_node( primitive: primitive.index(), }; let bounds = primitive.bounding_box(); + let parent_entity = parent.target_entity(); let mut mesh_entity = parent.spawn(( // TODO: handle missing label handle errors here? @@ -1547,22 +1548,8 @@ fn load_node( let target_count = primitive.morph_targets().len(); if target_count != 0 { - let weights = match mesh.weights() { - Some(weights) => weights.to_vec(), - None => vec![0.0; target_count], - }; - - if morph_weights.is_none() { - morph_weights = Some(weights.clone()); - } - - // unwrap: the parent's call to `MeshMorphWeights::new` - // means this code doesn't run if it returns an `Err`. - // According to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#morph-targets - // they should all have the same length. - // > All morph target accessors MUST have the same count as - // > the accessors of the original primitive. - mesh_entity.insert(MeshMorphWeights::new(weights).unwrap()); + max_morph_target_count = max_morph_target_count.max(target_count); + mesh_entity.insert(MeshMorphWeights(parent_entity)); } mesh_entity.insert(Aabb::from_min_max( Vec3::from_slice(&bounds.min), @@ -1700,15 +1687,31 @@ fn load_node( // Only include meshes in the output if they're set to be retained in the MAIN_WORLD and/or RENDER_WORLD by the load_meshes flag if !settings.load_meshes.is_empty() - && let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights) + && let Some(mesh) = gltf_node.mesh() { - let primitive_label = mesh.primitives().next().map(|p| GltfAssetLabel::Primitive { - mesh: mesh.index(), - primitive: p.index(), - }); - let first_mesh = - primitive_label.map(|label| load_context.get_label_handle(label.to_string())); - node.insert(MorphWeights::new(weights, first_mesh)?); + // Create the `MorphWeights` component. The weights will be copied + // from `mesh.weights()` if present. If not then the weights are + // zero. + // + // The glTF spec says that all primitives within a mesh must have + // the same number of morph targets, and `mesh.weights()` should be + // equal to that number if present. We're more forgiving and take + // whichever is largest, leaving any unspecified weights at zero. + if (max_morph_target_count > 0) || mesh.weights().is_some() { + let mut weights = Vec::from(mesh.weights().unwrap_or(&[])); + + if max_morph_target_count > weights.len() { + weights.resize(max_morph_target_count, 0.0); + } + + let primitive_label = mesh.primitives().next().map(|p| GltfAssetLabel::Primitive { + mesh: mesh.index(), + primitive: p.index(), + }); + let first_mesh = + primitive_label.map(|label| load_context.get_label_handle(label.to_string())); + node.insert(MorphWeights::new(weights, first_mesh)?); + } } if let Some(err) = gltf_error { diff --git a/crates/bevy_mesh/src/lib.rs b/crates/bevy_mesh/src/lib.rs index c97fb21422a6e..fb8275f90c04e 100644 --- a/crates/bevy_mesh/src/lib.rs +++ b/crates/bevy_mesh/src/lib.rs @@ -12,7 +12,6 @@ pub mod morph; pub mod primitives; pub mod skinning; mod vertex; -use bevy_ecs::schedule::SystemSet; use bitflags::bitflags; pub use components::*; pub use index::*; @@ -68,7 +67,3 @@ impl BaseMeshPipelineKey { } } } - -/// `bevy_render::mesh::inherit_weights` runs in this `SystemSet` -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] -pub struct InheritWeights; diff --git a/crates/bevy_mesh/src/morph.rs b/crates/bevy_mesh/src/morph.rs index fdeeeacc31198..78c13e91b4ed2 100644 --- a/crates/bevy_mesh/src/morph.rs +++ b/crates/bevy_mesh/src/morph.rs @@ -97,17 +97,64 @@ impl MorphTargetImage { } } -/// Controls the [morph targets] for all child `Mesh3d` entities. In most cases, [`MorphWeights`] should be considered -/// the "source of truth" when writing morph targets for meshes. However you can choose to write child [`MeshMorphWeights`] -/// if your situation requires more granularity. Just note that if you set [`MorphWeights`], it will overwrite child -/// [`MeshMorphWeights`] values. +/// A component that controls the [morph targets] of one or more `Mesh3d` +/// components. /// -/// This exists because Bevy's [`Mesh`] corresponds to a _single_ surface / material, whereas morph targets -/// as defined in the GLTF spec exist on "multi-primitive meshes" (where each primitive is its own surface with its own material). -/// Therefore in Bevy [`MorphWeights`] an a parent entity are the "canonical weights" from a GLTF perspective, which then -/// synchronized to child `Mesh3d` / [`MeshMorphWeights`] (which correspond to "primitives" / "surfaces" from a GLTF perspective). +/// To find the weights of its morph targets, a `Mesh3d` component looks for a +/// [`MeshMorphWeights`] component in the same entity. This points to another +/// entity, which is expected to contain a `MorphWeights` component. /// -/// Add this to the parent of one or more [`Entities`](`Entity`) with a `Mesh3d` with a [`MeshMorphWeights`]. +/// The intermediate `MeshMorphWeights` component allows multiple `Mesh3d` +/// components to share one `MorphWeights` component. +/// +/// The example shows a single mesh entity with a separate weights entity: +/// +/// ``` +/// # use bevy_asset::prelude::*; +/// # use bevy_ecs::prelude::*; +/// # use bevy_mesh::Mesh; +/// # use bevy_mesh::morph::*; +/// # #[derive(Component)] +/// # struct Mesh3d(Handle); +/// fn setup(mut commands: Commands, mesh_handle: Handle) { +/// // Create the `MorphWeights` component. +/// let weights_component = MorphWeights::new( +/// vec![0.0, 0.5, 1.0], +/// None, +/// ).unwrap(); +/// +/// // Spawn an entity to contain the weights. +/// let weights_entity = commands.spawn(weights_component).id(); +/// +/// // Spawn an entity with a mesh and a `MeshMorphWeights` component that +/// // points to `weights_entity`. +/// let mesh_entity = commands.spawn(( +/// Mesh3d(mesh_handle.clone()), +/// MeshMorphWeights(weights_entity), +/// )); +/// } +/// ``` +/// +/// In the simplest case, all the components can be in one entity: +/// +/// ``` +/// # use bevy_asset::prelude::*; +/// # use bevy_ecs::prelude::*; +/// # use bevy_mesh::Mesh; +/// # use bevy_mesh::morph::*; +/// # #[derive(Component)] +/// # struct Mesh3d(Handle); +/// # fn setup(mut commands: Commands, mesh_entity: Entity) { +/// # let weights_component = MorphWeights::new(vec![0.0, 0.5, 1.0], None).unwrap(); +/// # let mesh_handle = Handle::::default(); +/// let weights_entity = commands.spawn(weights_component).id(); +/// +/// commands.entity(weights_entity).insert(( +/// Mesh3d(mesh_handle.clone()), +/// MeshMorphWeights(weights_entity), +/// )); +/// # } +/// ``` /// /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation #[derive(Reflect, Default, Debug, Clone, Component)] @@ -143,39 +190,6 @@ impl MorphWeights { pub fn weights_mut(&mut self) -> &mut [f32] { &mut self.weights } -} - -/// Control a specific [`Mesh`] instance's [morph targets]. These control the weights of -/// specific "mesh primitives" in scene formats like GLTF. They can be set manually, but -/// in most cases they should "automatically" synced by setting the [`MorphWeights`] component -/// on a parent entity. -/// -/// See [`MorphWeights`] for more details on Bevy's morph target implementation. -/// -/// Add this to an [`Entity`] with a `Mesh3d` with a [`MorphAttributes`] set -/// to control individual weights of each morph target. -/// -/// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation -#[derive(Reflect, Default, Debug, Clone, Component)] -#[reflect(Debug, Component, Default, Clone)] -pub struct MeshMorphWeights { - weights: Vec, -} - -impl MeshMorphWeights { - pub fn new(weights: Vec) -> Result { - if weights.len() > MAX_MORPH_WEIGHTS { - let target_count = weights.len(); - return Err(MorphBuildError::TooManyTargets { target_count }); - } - Ok(MeshMorphWeights { weights }) - } - pub fn weights(&self) -> &[f32] { - &self.weights - } - pub fn weights_mut(&mut self) -> &mut [f32] { - &mut self.weights - } pub fn clear_weights(&mut self) { self.weights.clear(); } @@ -184,6 +198,16 @@ impl MeshMorphWeights { } } +/// Controls the [morph targets] of a `Mesh3d` component by referencing an +/// entity with a `MorphWeights` component. +/// +/// See [`MorphWeights`] for examples. +/// +/// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation +#[derive(Reflect, Debug, Clone, Component)] +#[reflect(Debug, Component, Clone)] +pub struct MeshMorphWeights(#[entities] pub Entity); + /// Attributes **differences** used for morph targets. /// /// See [`MorphTargetImage`] for more information. diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index b2dc90af02294..0e571039386e7 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -2,7 +2,7 @@ use core::{iter, mem}; use bevy_camera::visibility::ViewVisibility; use bevy_ecs::prelude::*; -use bevy_mesh::morph::{MeshMorphWeights, MAX_MORPH_WEIGHTS}; +use bevy_mesh::morph::{MeshMorphWeights, MorphWeights, MAX_MORPH_WEIGHTS}; use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ batching::NoAutomaticBatching, @@ -110,6 +110,7 @@ pub fn extract_morphs( morph_indices: ResMut, uniform: ResMut, query: Extract>, + weights_query: Extract>, ) { // Borrow check workaround. let (morph_indices, uniform) = (morph_indices.into_inner(), uniform.into_inner()); @@ -125,9 +126,11 @@ pub fn extract_morphs( if !view_visibility.get() { continue; } + let Ok(weights) = weights_query.get(morph_weights.0) else { + continue; + }; let start = uniform.current_buffer.len(); - let weights = morph_weights.weights(); - let legal_weights = weights.iter().take(MAX_MORPH_WEIGHTS).copied(); + let legal_weights = weights.weights().iter().take(MAX_MORPH_WEIGHTS).copied(); uniform.current_buffer.extend(legal_weights); add_to_alignment::(&mut uniform.current_buffer); diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 6227d25a454db..85b01cb06f611 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -102,7 +102,7 @@ pub use extract_param::Extract; use crate::{ camera::CameraPlugin, gpu_readback::GpuReadbackPlugin, - mesh::{MeshPlugin, MorphPlugin, RenderMesh}, + mesh::{MeshPlugin, RenderMesh}, render_asset::prepare_assets, render_resource::{init_empty_bind_group_layout, PipelineCache, Shader, ShaderLoader}, renderer::{render_system, RenderInstance}, @@ -457,7 +457,6 @@ impl Plugin for RenderPlugin { ViewPlugin, MeshPlugin, GlobalsPlugin, - MorphPlugin, BatchingPlugin { debug_flags: self.debug_flags, }, diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 43cf2dedf4a9b..e8679e4d917be 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -16,7 +16,6 @@ use bevy_ecs::{ SystemParamItem, }, }; -use bevy_mesh::morph::{MeshMorphWeights, MorphWeights}; pub use bevy_mesh::*; use wgpu::IndexFormat; @@ -46,32 +45,6 @@ impl Plugin for MeshPlugin { } } -/// [Inherit weights](inherit_weights) from glTF mesh parent entity to direct -/// bevy mesh child entities (ie: glTF primitive). -pub struct MorphPlugin; -impl Plugin for MorphPlugin { - fn build(&self, app: &mut App) { - app.add_systems(PostUpdate, inherit_weights.in_set(InheritWeights)); - } -} - -/// Bevy meshes are gltf primitives, [`MorphWeights`] on the bevy node entity -/// should be inherited by children meshes. -/// -/// Only direct children are updated, to fulfill the expectations of glTF spec. -pub fn inherit_weights( - morph_nodes: Query<(&Children, &MorphWeights), (Without, Changed)>, - mut morph_primitives: Query<&mut MeshMorphWeights, With>, -) { - for (children, parent_weights) in &morph_nodes { - let mut iter = morph_primitives.iter_many_mut(children); - while let Some(mut child_weight) = iter.fetch_next() { - child_weight.clear_weights(); - child_weight.extend_weights(parent_weights.weights()); - } - } -} - /// The render world representation of a [`Mesh`]. #[derive(Debug, Clone)] pub struct RenderMesh { diff --git a/release-content/migration-guides/meshmorphweights_is_now_a_reference.md b/release-content/migration-guides/meshmorphweights_is_now_a_reference.md new file mode 100644 index 0000000000000..c2892ce7a1471 --- /dev/null +++ b/release-content/migration-guides/meshmorphweights_is_now_a_reference.md @@ -0,0 +1,27 @@ +--- +title: `MeshMorphWeights` is now a reference, and `MorphPlugin` has been removed +pull_requests: [18465] +--- + +Some morph target components have been changed to improve performance. Users who +let the glTF loader set up their components do not need to make any changes. +Users who set up their morph target components manually or modify the glTF +loader's components may need to make changes. + +`MeshMorphWeights` is now a reference to an entity with a `MorphWeights` +component. Previously it contained a copy of the weights. + +```diff +- struct MeshMorphWeights { weights: Vec } ++ struct MeshMorphWeights(Entity); +``` + +See the `MorphWeights` documentation for examples of how to set up morph targets +with the new structure. + +`MorphPlugin` has been removed as it was no longer necessary. Users who added +this plugin manually can remove it. + +```diff +- app.add_plugins(MorphPlugin); +```