diff --git a/assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_cal.csv b/assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_cal.morph_anim.csv similarity index 100% rename from assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_cal.csv rename to assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_cal.morph_anim.csv diff --git a/assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_neutral.csv b/assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_neutral.morph_anim.csv similarity index 100% rename from assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_neutral.csv rename to assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_neutral.morph_anim.csv diff --git a/assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_raw.csv b/assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_raw.morph_anim.csv similarity index 100% rename from assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_raw.csv rename to assets/20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_raw.morph_anim.csv diff --git a/examples/simple_example.rs b/examples/simple_example.rs index 9537bea..023d23e 100644 --- a/examples/simple_example.rs +++ b/examples/simple_example.rs @@ -1,7 +1,10 @@ +#![allow(clippy::type_complexity)] + use bevy::{ pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}, prelude::*, }; +use bevy_facial_mocap::{CSVAnimationLoader, CSVAnimationLoaderSettings}; use bevy_inspector_egui::quick::WorldInspectorPlugin; fn main() { @@ -9,10 +12,15 @@ fn main() { .insert_resource(DirectionalLightShadowMap { size: 4096 }) .add_plugins(DefaultPlugins) .add_plugins(WorldInspectorPlugin::new()) + .init_asset_loader::() .add_systems(Startup, setup) + .add_systems(Update, setup_animation) .run(); } +#[derive(Component)] +struct AnimationRunning(Handle); + fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera3dBundle { transform: Transform::from_xyz(0.7, 0.7, 1.0).looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y), @@ -42,3 +50,13 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }); } + +fn setup_animation(mut commands: Commands, asset_server: Res, mut players: Query, Without)>) { + for entity in &mut players { + let handle = asset_server.load_with_settings("20240205_shapekey_BevyImplementationFaceCap_5/shapekey_BevyImplementationFaceCap_5_ScottiPhone_raw.morph_anim.csv", |s: &mut CSVAnimationLoaderSettings| { s.0.parts = vec![Name::new("pascal_Head")]; }); + let mut player = AnimationPlayer::default(); + player.play(handle.clone()).repeat(); + + commands.entity(entity).insert((AnimationRunning(handle), player)); + } +} diff --git a/src/lib.rs b/src/lib.rs index 4f68f22..d31d255 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,9 @@ use std::{num::ParseFloatError, str::FromStr}; use bevy::{ - animation::{AnimationClip, EntityPath, Keyframes, VariableCurve}, - asset::{Asset, AssetLoader, AsyncReadExt}, - reflect::Reflect, + animation::{self, AnimationClip, EntityPath, Keyframes, VariableCurve}, asset::{meta::Settings, Asset, AssetLoader, AsyncReadExt}, core::Name, reflect::Reflect }; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[derive( Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Reflect, Default, @@ -222,10 +220,10 @@ impl From for VariableCurve { } } -impl From for AnimationClip { - fn from(val: CSVAnimation) -> Self { - let mut clip = Self::default(); - clip.add_curve_to_path(EntityPath::default(), val.into()); +impl CSVAnimation { + fn generate_clip(self, path: &EntityPath) -> AnimationClip { + let mut clip = AnimationClip::default(); + clip.add_curve_to_path(path.clone(), self.into()); clip } @@ -249,17 +247,38 @@ impl std::fmt::Display for CSVAnimationLoaderError { impl std::error::Error for CSVAnimationLoaderError {} +#[derive(Default)] +pub struct CSVAnimationLoaderSettings(pub EntityPath); + +impl Serialize for CSVAnimationLoaderSettings { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + let names = self.0.parts.iter().map(|name| name.to_string()).collect::>(); + names.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for CSVAnimationLoaderSettings { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> { + let names = Vec::::deserialize(deserializer)?; + Ok(CSVAnimationLoaderSettings(EntityPath { parts: names.into_iter().map(|name| Name::new(name)).collect() })) + } +} + impl AssetLoader for CSVAnimationLoader { type Asset = AnimationClip; - type Settings = (); + type Settings = CSVAnimationLoaderSettings; type Error = CSVAnimationLoaderError; fn load<'a>( &'a self, reader: &'a mut bevy::asset::io::Reader, - _settings: &'a Self::Settings, + settings: &'a Self::Settings, _load_context: &'a mut bevy::asset::LoadContext, ) -> bevy::utils::BoxedFuture<'a, Result> { Box::pin(async move { @@ -270,7 +289,10 @@ impl AssetLoader for CSVAnimationLoader { .map_err(CSVAnimationLoaderError::Io)?; let csv_animation = CSVAnimation::parse(&csv).map_err(CSVAnimationLoaderError::CSVParseError)?; - Ok(csv_animation.into()) + + let animation = csv_animation.generate_clip(&settings.0); + + Ok(animation) }) } @@ -423,7 +445,7 @@ mod tests { 00:00:01:01.00, 0, 0, 0", ) .unwrap(); - let clip: AnimationClip = animation.into(); + let clip: AnimationClip = animation.generate_clip(&EntityPath::default()); assert!(clip.duration() - 1.33333 < 0.1); let curves = clip .get_curves_by_path(&EntityPath::default())