Skip to content

Commit

Permalink
Load morph targets from gltf
Browse files Browse the repository at this point in the history
  • Loading branch information
nicopap committed Apr 10, 2023
1 parent e22572d commit d657f73
Show file tree
Hide file tree
Showing 36 changed files with 1,502 additions and 187 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -226,6 +226,7 @@ anyhow = "1.0.4"
rand = "0.8.0"
ron = "0.8.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
bytemuck = "1.7"
# Needed to poll Task examples
futures-lite = "1.11.3"
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_animation/Cargo.toml
Expand Up @@ -15,6 +15,7 @@ bevy_asset = { path = "../bevy_asset", version = "0.11.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.11.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.11.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.11.0-dev", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.11.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.11.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.11.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.11.0-dev" }
Expand Down
56 changes: 54 additions & 2 deletions crates/bevy_animation/src/lib.rs
Expand Up @@ -12,6 +12,7 @@ use bevy_ecs::prelude::*;
use bevy_hierarchy::{Children, Parent};
use bevy_math::{Quat, Vec3};
use bevy_reflect::{FromReflect, Reflect, TypeUuid};
use bevy_render::mesh::morph::MorphWeights;
use bevy_time::Time;
use bevy_transform::{prelude::Transform, TransformSystem};
use bevy_utils::{tracing::warn, HashMap};
Expand All @@ -33,6 +34,11 @@ pub enum Keyframes {
Translation(Vec<Vec3>),
/// Keyframes for scale.
Scale(Vec<Vec3>),
/// Keyframes for morph target weights.
///
/// Note that in `.0`, each contiguous `target_count` values is a single
/// keyframe representing the weight values at given keyframe.
Weights(Vec<f32>),
}

/// Describes how an attribute of a [`Transform`] should be animated.
Expand Down Expand Up @@ -269,7 +275,7 @@ impl AnimationPlayer {
}
}

fn find_bone(
fn entity_from_path(
root: Entity,
path: &EntityPath,
children: &Query<&Children>,
Expand Down Expand Up @@ -335,12 +341,14 @@ fn verify_no_ancestor_player(

/// System that will play all animations, using any entity with a [`AnimationPlayer`]
/// and a [`Handle<AnimationClip>`] as an animation root
#[allow(clippy::too_many_arguments)]
pub fn animation_player(
time: Res<Time>,
animations: Res<Assets<AnimationClip>>,
children: Query<&Children>,
names: Query<&Name>,
transforms: Query<&mut Transform>,
morphs: Query<&mut MorphWeights>,
parents: Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
mut animation_players: Query<(Entity, Option<&Parent>, &mut AnimationPlayer)>,
) {
Expand All @@ -355,6 +363,7 @@ pub fn animation_player(
&animations,
&names,
&transforms,
&morphs,
maybe_parent,
&parents,
&children,
Expand All @@ -370,6 +379,7 @@ fn run_animation_player(
animations: &Assets<AnimationClip>,
names: &Query<&Name>,
transforms: &Query<&mut Transform>,
morphs: &Query<&mut MorphWeights>,
maybe_parent: Option<&Parent>,
parents: &Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
children: &Query<&Children>,
Expand All @@ -391,6 +401,7 @@ fn run_animation_player(
animations,
names,
transforms,
morphs,
maybe_parent,
parents,
children,
Expand All @@ -412,13 +423,39 @@ fn run_animation_player(
animations,
names,
transforms,
morphs,
maybe_parent,
parents,
children,
);
}
}

/// # Safety
///
/// No other reference to a `MorphWeights` accessible through `morphs` should
/// exist when this function is called.
unsafe fn morph_primitives(
children: &Children,
morphs: &Query<&mut MorphWeights>,
weight: f32,
keyframes: &[f32],
offset: usize,
) {
for child in children {
// SAFETY: ensured by function's safety invariants.
let Ok(mut morph) = (unsafe { morphs.get_unchecked(*child) }) else { continue };
let target_count = morph.weights().len();
let start = target_count * offset;
let end = target_count * (offset + 1);
let zipped = morph.weights_mut().iter_mut().zip(&keyframes[start..end]);
for (morph_weight, keyframe) in zipped {
let minus_weight = 1.0 - weight;
*morph_weight = (*morph_weight * minus_weight) + (keyframe * weight);
}
}
}

#[allow(clippy::too_many_arguments)]
fn apply_animation(
weight: f32,
Expand All @@ -429,6 +466,7 @@ fn apply_animation(
animations: &Assets<AnimationClip>,
names: &Query<&Name>,
transforms: &Query<&mut Transform>,
morphs: &Query<&mut MorphWeights>,
maybe_parent: Option<&Parent>,
parents: &Query<(Option<With<AnimationPlayer>>, Option<&Parent>)>,
children: &Query<&Children>,
Expand All @@ -455,7 +493,7 @@ fn apply_animation(
for (path, bone_id) in &animation_clip.paths {
let cached_path = &mut animation.path_cache[*bone_id];
let curves = animation_clip.get_curves(*bone_id).unwrap();
let Some(target) = find_bone(root, path, children, names, cached_path) else { continue };
let Some(target) = entity_from_path(root, path, children, names, cached_path) else { continue };
// SAFETY: The verify_no_ancestor_player check above ensures that two animation players cannot alias
// any of their descendant Transforms.
//
Expand Down Expand Up @@ -483,6 +521,13 @@ fn apply_animation(
Keyframes::Scale(keyframes) => {
transform.scale = transform.scale.lerp(keyframes[0], weight);
}
Keyframes::Weights(keyframes) => {
let Ok(children) = children.get(target) else { continue; };
// SAFETY: Same as above
unsafe {
morph_primitives(children, morphs, weight, keyframes, 0);
};
}
}
continue;
}
Expand Down Expand Up @@ -528,6 +573,13 @@ fn apply_animation(
let result = scale_start.lerp(scale_end, lerp);
transform.scale = transform.scale.lerp(result, weight);
}
Keyframes::Weights(keyframes) => {
let Ok(children) = children.get(target) else { continue; };
// SAFETY: Same as above
unsafe {
morph_primitives(children, morphs, weight, keyframes, step_start)
};
}
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_gltf/Cargo.toml
Expand Up @@ -37,5 +37,8 @@ gltf = { version = "1.0.0", default-features = false, features = [
] }
thiserror = "1.0"
anyhow = "1.0.4"
# For Zeroable and Pod traits used to generate morph target buffer
bytemuck = { version = "1.5", features = ["derive"] }

base64 = "0.13.0"
percent-encoding = "2.1"
9 changes: 9 additions & 0 deletions crates/bevy_gltf/src/lib.rs
Expand Up @@ -21,6 +21,7 @@ impl Plugin for GltfPlugin {
fn build(&self, app: &mut App) {
app.init_asset_loader::<GltfLoader>()
.register_type::<GltfExtras>()
.register_type::<GltfMeshExtras>()
.add_asset::<Gltf>()
.add_asset::<GltfNode>()
.add_asset::<GltfPrimitive>()
Expand Down Expand Up @@ -82,3 +83,11 @@ pub struct GltfPrimitive {
pub struct GltfExtras {
pub value: String,
}
/// Gltf `extras` field present in the gltf `mesh` of this node.
///
/// This allows accessing the `extras` field of a mesh as a component.
#[derive(Clone, Debug, Reflect, Default, Component)]
#[reflect(Component)]
pub struct GltfMeshExtras {
pub value: String,
}
70 changes: 55 additions & 15 deletions crates/bevy_gltf/src/loader.rs
@@ -1,3 +1,5 @@
mod morph;

use anyhow::Result;
use bevy_asset::{
AssetIoError, AssetLoader, AssetPath, BoxedFuture, Handle, LoadContext, LoadedAsset,
Expand All @@ -16,6 +18,7 @@ use bevy_render::{
camera::{Camera, OrthographicProjection, PerspectiveProjection, Projection, ScalingMode},
color::Color,
mesh::{
morph::MorphWeights,
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
Indices, Mesh, VertexAttributeValues,
},
Expand Down Expand Up @@ -64,6 +67,8 @@ pub enum GltfError {
MissingAnimationSampler(usize),
#[error("failed to generate tangents: {0}")]
GenerateTangentsError(#[from] bevy_render::mesh::GenerateTangentsError),
#[error("failed to generate morph targets: {0}")]
MorphTarget(#[from] bevy_render::mesh::morph::MorphTargetsGenerationError),
}

/// Loads glTF files with all of their data as their corresponding bevy representations.
Expand Down Expand Up @@ -146,6 +151,7 @@ async fn load_gltf<'a, 'b>(

#[cfg(feature = "bevy_animation")]
let (animations, named_animations, animation_roots) = {
use gltf::animation::util::ReadOutputs;
let mut animations = vec![];
let mut named_animations = HashMap::default();
let mut animation_roots = HashSet::default();
Expand Down Expand Up @@ -176,20 +182,17 @@ async fn load_gltf<'a, 'b>(

let keyframes = if let Some(outputs) = reader.read_outputs() {
match outputs {
gltf::animation::util::ReadOutputs::Translations(tr) => {
ReadOutputs::Translations(tr) => {
bevy_animation::Keyframes::Translation(tr.map(Vec3::from).collect())
}
gltf::animation::util::ReadOutputs::Rotations(rots) => {
bevy_animation::Keyframes::Rotation(
rots.into_f32().map(bevy_math::Quat::from_array).collect(),
)
}
gltf::animation::util::ReadOutputs::Scales(scale) => {
ReadOutputs::Rotations(rots) => bevy_animation::Keyframes::Rotation(
rots.into_f32().map(bevy_math::Quat::from_array).collect(),
),
ReadOutputs::Scales(scale) => {
bevy_animation::Keyframes::Scale(scale.map(Vec3::from).collect())
}
gltf::animation::util::ReadOutputs::MorphTargetWeights(_) => {
warn!("Morph animation property not yet supported");
continue;
ReadOutputs::MorphTargetWeights(weights) => {
bevy_animation::Keyframes::Weights(weights.into_f32().collect())
}
}
} else {
Expand Down Expand Up @@ -233,6 +236,7 @@ async fn load_gltf<'a, 'b>(
let mut primitives = vec![];
for primitive in mesh.primitives() {
let primitive_label = primitive_label(&mesh, &primitive);
let morph_targets_label = morph_targets_label(&mesh, &primitive);
let reader = primitive.reader(|buffer| Some(&buffer_data[buffer.index()]));
let primitive_topology = get_primitive_topology(primitive.mode())?;

Expand Down Expand Up @@ -282,6 +286,15 @@ async fn load_gltf<'a, 'b>(
mesh.set_indices(Some(Indices::U32(indices.into_u32().collect())));
};

let target_count = reader.read_morph_targets().len();
if target_count != 0 {
let walker = morph::PrimitiveMorphTargets::new(&reader);
let store = |image| {
load_context.set_labeled_asset(&morph_targets_label, LoadedAsset::new(image))
};
mesh.set_morph_targets(walker, store)?;
}

if mesh.attribute(Mesh::ATTRIBUTE_NORMAL).is_none()
&& matches!(mesh.primitive_topology(), PrimitiveTopology::TriangleList)
{
Expand Down Expand Up @@ -767,6 +780,16 @@ fn load_node(
// Map node index to entity
node_index_to_entity_map.insert(gltf_node.index(), node.id());

if let Some(mesh) = gltf_node.mesh() {
if let Some(extras) = mesh.extras().as_ref() {
node.insert(super::GltfMeshExtras {
value: extras.get().to_string(),
});
}
if let Some(weights) = mesh.weights() {
node.insert(MorphWeights::new(weights.to_vec()));
}
};
node.with_children(|parent| {
if let Some(mesh) = gltf_node.mesh() {
// append primitives
Expand All @@ -788,27 +811,35 @@ fn load_node(
let material_asset_path =
AssetPath::new_ref(load_context.path(), Some(&material_label));

let mut mesh_entity = parent.spawn(PbrBundle {
let mut primitive_entity = parent.spawn(PbrBundle {
mesh: load_context.get_handle(mesh_asset_path),
material: load_context.get_handle(material_asset_path),
..Default::default()
});
mesh_entity.insert(Aabb::from_min_max(
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],
};
primitive_entity.insert(MorphWeights::new(weights));
}
primitive_entity.insert(Aabb::from_min_max(
Vec3::from_slice(&bounds.min),
Vec3::from_slice(&bounds.max),
));

if let Some(extras) = primitive.extras() {
mesh_entity.insert(super::GltfExtras {
primitive_entity.insert(super::GltfExtras {
value: extras.get().to_string(),
});
}
if let Some(name) = mesh.name() {
mesh_entity.insert(Name::new(name.to_string()));
primitive_entity.insert(Name::new(name.to_string()));
}
// Mark for adding skinned mesh
if let Some(skin) = gltf_node.skin() {
entity_to_skin_index_map.insert(mesh_entity.id(), skin.index());
entity_to_skin_index_map.insert(primitive_entity.id(), skin.index());
}
}
}
Expand Down Expand Up @@ -921,6 +952,15 @@ fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String {
format!("Mesh{}/Primitive{}", mesh.index(), primitive.index())
}

/// Returns the label for the `mesh` and `primitive`.
fn morph_targets_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String {
format!(
"Mesh{}/Primitive{}/MorphTargets",
mesh.index(),
primitive.index()
)
}

/// Returns the label for the `material`.
fn material_label(material: &gltf::Material) -> String {
if let Some(index) = material.index() {
Expand Down

0 comments on commit d657f73

Please sign in to comment.