diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index 11e819806c506..5842e06456d35 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -20,7 +20,6 @@ bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [ "petgraph", ] } -bevy_render = { path = "../bevy_render", version = "0.16.0-dev" } bevy_time = { path = "../bevy_time", version = "0.16.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" } diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 43ea343aa311c..37be1608ab348 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -1253,9 +1253,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_render::mesh::inherit_weights) - .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 a4e25475b74fd..8ddbd2e68b772 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -1363,7 +1363,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 @@ -1389,6 +1389,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? @@ -1400,22 +1401,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), @@ -1548,14 +1535,30 @@ 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() { - if let (Some(mesh), Some(weights)) = (gltf_node.mesh(), morph_weights) { - 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(mesh) = gltf_node.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)?); + } } } diff --git a/crates/bevy_mesh/src/morph.rs b/crates/bevy_mesh/src/morph.rs index a8ff3be037d5a..b3bb4f7a142a5 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)] @@ -142,38 +189,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(); } @@ -182,6 +197,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 29070724dd51c..44d1123fe9936 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -4,7 +4,7 @@ use bevy_ecs::prelude::*; use bevy_render::sync_world::MainEntityHashMap; use bevy_render::{ batching::NoAutomaticBatching, - mesh::morph::{MeshMorphWeights, MAX_MORPH_WEIGHTS}, + mesh::morph::{MeshMorphWeights, MorphWeights, MAX_MORPH_WEIGHTS}, render_resource::{BufferUsages, RawBufferVec}, renderer::{RenderDevice, RenderQueue}, view::ViewVisibility, @@ -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/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index fbd530c14da42..a1c0234456ba9 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -84,31 +84,12 @@ impl Plugin for MeshPlugin { } } -/// [Inherit weights](inherit_weights) from glTF mesh parent entity to direct -/// bevy mesh child entities (ie: glTF primitive). +/// Adds morph target types. pub struct MorphPlugin; impl Plugin for MorphPlugin { fn build(&self, app: &mut App) { app.register_type::() - .register_type::() - .add_systems(PostUpdate, inherit_weights); - } -} - -/// 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()); - } + .register_type::(); } } 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..c038f6d203a5b --- /dev/null +++ b/release-content/migration-guides/meshmorphweights_is_now_a_reference.md @@ -0,0 +1,16 @@ +--- +title: `MeshMorphWeights` is now a reference +pull_requests: [18465] +--- + +`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); +``` + +This change was made to improve runtime and compile-time performance. See the +`MorphWeights` documentation for examples of how to set up morph targets with +the new convention.