diff --git a/Cargo.toml b/Cargo.toml index 332db71..b333ef6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0" readme = "README.md" [dependencies] -bevy = { version = "0.15", features = [ +bevy = { path = "../bevy", features = [ "file_watcher", "bevy_picking", "serialize", @@ -19,9 +19,6 @@ serde = "1" serde_json = "1" anyhow = "1" -[dev-dependencies] -bevy-inspector-egui = "0.30.0" - [lints.clippy] type_complexity = "allow" doc_markdown = "warn" diff --git a/assets/models/AliciaSolid.vrm b/assets/models/AliciaSolid.vrm new file mode 100644 index 0000000..1142f10 Binary files /dev/null and b/assets/models/AliciaSolid.vrm differ diff --git a/examples/simple.rs b/examples/simple.rs index 059aece..50f3c61 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,12 +1,16 @@ use bevy::prelude::*; -use bevy_inspector_egui::quick::WorldInspectorPlugin; +/*use bevy_inspector_egui::quick::WorldInspectorPlugin;*/ use bevy_vrma::vrm::loader::VrmHandle; -use bevy_vrma::vrm::VrmPlugin; +use bevy_vrma::vrm::{VrmBone, VrmPlugin}; fn main() { App::new() - .add_plugins((DefaultPlugins, WorldInspectorPlugin::default(), VrmPlugin)) + .add_plugins(( + DefaultPlugins, + /* WorldInspectorPlugin::default(), */ VrmPlugin, + )) .add_systems(Startup, (spawn_camera, spawn_vrm)) + .add_systems(Update, bone_names) .run(); } @@ -18,5 +22,11 @@ fn spawn_vrm( mut commands: Commands, asset_server: Res, ) { - commands.spawn(VrmHandle(asset_server.load("models/sample.vrm"))); + commands.spawn(VrmHandle(asset_server.load("models/AliciaSolid2.vrm"))); +} + +fn bone_names(query: Query<&VrmBone>) { + for bone in query.iter() { + //println!("{}", bone.0); + } } diff --git a/examples/vrma.rs b/examples/vrma.rs index 84dcac9..4b706bd 100644 --- a/examples/vrma.rs +++ b/examples/vrma.rs @@ -63,7 +63,7 @@ fn spawn_vrm( }; commands - .spawn(VrmHandle(asset_server.load("models/sample.vrm"))) + .spawn(VrmHandle(asset_server.load("models/AliciaSolid.vrm"))) .with_children(|cmd| { vrma(1, cmd); vrma(2, cmd); diff --git a/src/system_param/child_searcher.rs b/src/system_param/child_searcher.rs index b36a51f..aafbca5 100644 --- a/src/system_param/child_searcher.rs +++ b/src/system_param/child_searcher.rs @@ -1,7 +1,6 @@ use crate::vrm::VrmBone; -use bevy::core::Name; use bevy::ecs::system::SystemParam; -use bevy::prelude::{Children, Entity, Query}; +use bevy::prelude::{Children, Entity, Name, Query}; #[derive(SystemParam)] pub struct ChildSearcher<'w, 's> { @@ -17,6 +16,14 @@ pub struct ChildSearcher<'w, 's> { } impl ChildSearcher<'_, '_> { + #[inline] + pub fn has_not_root_bone( + &self, + root: Entity, + ) -> bool { + self.find_from_name(root, "Root").is_none() + } + pub fn find_from_name( &self, root: Entity, diff --git a/src/vrm/expressions.rs b/src/vrm/expressions.rs index f036032..039502d 100644 --- a/src/vrm/expressions.rs +++ b/src/vrm/expressions.rs @@ -3,10 +3,9 @@ use crate::vrm::extensions::VrmExtensions; use crate::vrm::VrmExpression; use bevy::app::Plugin; use bevy::asset::{Assets, Handle}; -use bevy::core::Name; use bevy::gltf::GltfNode; -use bevy::prelude::{Component, Deref, Reflect}; -use bevy::utils::HashMap; +use bevy::platform_support::collections::HashMap; +use bevy::prelude::{Component, Deref, Name, Reflect}; #[derive(Reflect, Debug, Clone)] pub struct ExpressionNode { diff --git a/src/vrm/extensions/vrmc_spring_bone.rs b/src/vrm/extensions/vrmc_spring_bone.rs index 96e1200..3cf418d 100644 --- a/src/vrm/extensions/vrmc_spring_bone.rs +++ b/src/vrm/extensions/vrmc_spring_bone.rs @@ -96,6 +96,12 @@ pub enum ColliderShape { Capsule(Capsule), } +impl Default for ColliderShape { + fn default() -> Self { + Self::Sphere(Sphere::default()) + } +} + impl ColliderShape { /// Returns the collision vector from the collider to the target position. pub fn calc_collision( @@ -129,7 +135,7 @@ impl ColliderShape { } } -#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Component, Reflect)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Component, Reflect, Default)] #[reflect(Component, Serialize, Deserialize)] pub struct Sphere { /// Local coordinate of the sphere center @@ -139,7 +145,7 @@ pub struct Sphere { } /// 楕円形の -#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Component, Reflect)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Component, Reflect, Default)] #[reflect(Component, Serialize, Deserialize)] pub struct Capsule { /// Local coordinate of the center of the half sphere at the start point of the capsule diff --git a/src/vrm/extensions/vrmc_vrm.rs b/src/vrm/extensions/vrmc_vrm.rs index c768a6b..ca49193 100644 --- a/src/vrm/extensions/vrmc_vrm.rs +++ b/src/vrm/extensions/vrmc_vrm.rs @@ -1,5 +1,5 @@ use crate::vrm::extensions::VrmNode; -use bevy::utils::HashMap; +use bevy::platform_support::collections::HashMap; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] diff --git a/src/vrm/humanoid_bone.rs b/src/vrm/humanoid_bone.rs index 28955c7..994ab89 100644 --- a/src/vrm/humanoid_bone.rs +++ b/src/vrm/humanoid_bone.rs @@ -3,11 +3,9 @@ use crate::vrm::extensions::VrmNode; use crate::vrm::{BoneRestGlobalTransform, BoneRestTransform, VrmBone, VrmHipsBoneTo}; use bevy::app::{App, Plugin, Update}; use bevy::asset::{Assets, Handle}; -use bevy::core::Name; use bevy::gltf::GltfNode; -use bevy::hierarchy::Children; +use bevy::platform_support::collections::HashMap; use bevy::prelude::*; -use bevy::utils::HashMap; use serde::{Deserialize, Serialize}; #[derive(Component, Reflect, Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] @@ -60,9 +58,13 @@ fn attach_bones( transforms: Query<(&Transform, &GlobalTransform)>, ) { for (vrm_entity, humanoid_bones) in vrm.iter() { - if searcher.find_from_name(vrm_entity, "Root").is_none() { + /*if searcher.find_from_name(vrm_entity, "Root").is_none() && + // for blender export names it Root_ instead of Root + searcher.find_from_name(vrm_entity, "Root_").is_none() && + searcher.find_from_name(vrm_entity, "Armature_rootJoint").is_none() { + println!("NO BONES"); continue; - } + }*/ for (bone, name) in humanoid_bones.iter() { let Some(bone_entity) = searcher.find_from_name(vrm_entity, name.as_str()) else { diff --git a/src/vrm/spawn.rs b/src/vrm/spawn.rs index 375c60e..b4032f7 100644 --- a/src/vrm/spawn.rs +++ b/src/vrm/spawn.rs @@ -6,10 +6,9 @@ use crate::vrm::spring_bone::registry::*; use crate::vrm::{Vrm, VrmPath}; use bevy::app::{App, Update}; use bevy::asset::Assets; -use bevy::core::Name; use bevy::gltf::GltfNode; use bevy::log::error; -use bevy::prelude::{Commands, Entity, Plugin, Query, Res}; +use bevy::prelude::{Commands, Entity, Name, Plugin, Query, Res}; use bevy::scene::SceneRoot; pub struct VrmSpawnPlugin; @@ -61,7 +60,11 @@ fn spawn_vrm( if let Some(spring_bone) = extensions.vrmc_spring_bone.as_ref() { cmd.insert(( - SpringJointRegistry::new(&spring_bone.all_joints(), &node_assets, &vrm.gltf.nodes), + SpringJointPropsRegistry::new( + &spring_bone.all_joints(), + &node_assets, + &vrm.gltf.nodes, + ), SpringColliderRegistry::new(&spring_bone.colliders, &node_assets, &vrm.gltf.nodes), SpringNodeRegistry::new(spring_bone, &node_assets, &vrm.gltf.nodes), )); diff --git a/src/vrm/spring_bone.rs b/src/vrm/spring_bone.rs index 768c2d1..cf8695a 100644 --- a/src/vrm/spring_bone.rs +++ b/src/vrm/spring_bone.rs @@ -33,10 +33,12 @@ pub struct SpringRoot { pub colliders: Vec, + /// If the spring chain has a center node, + /// the inertia of the spring bone is evaluated in the [`Center Space`](https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_springBone-1.0#center-space). pub center_node: Option, } -#[derive(Component, Reflect, Debug, Serialize, Deserialize, Copy, Clone)] +#[derive(Component, Reflect, Debug, Serialize, Deserialize, Copy, Clone, Default)] #[reflect(Component, Serialize, Deserialize)] pub struct SpringJointProps { pub drag_force: f32, diff --git a/src/vrm/spring_bone/attach.rs b/src/vrm/spring_bone/attach.rs index 1728328..43ee6fe 100644 --- a/src/vrm/spring_bone/attach.rs +++ b/src/vrm/spring_bone/attach.rs @@ -1,11 +1,12 @@ use crate::system_param::child_searcher::ChildSearcher; use crate::vrm::spring_bone::registry::{ - SpringColliderRegistry, SpringJointRegistry, SpringNodeRegistry, + SpringColliderRegistry, SpringJointPropsRegistry, SpringNodeRegistry, }; use crate::vrm::spring_bone::{SpringJointState, SpringRoot}; use bevy::app::{App, Update}; use bevy::math::NormedVectorSpace; -use bevy::prelude::{Added, Children, Entity, ParallelCommands, Plugin, Query, Transform}; +use bevy::prelude::*; +use serde::{Deserialize, Serialize}; pub struct SpringBoneAttachPlugin; @@ -14,24 +15,49 @@ impl Plugin for SpringBoneAttachPlugin { &self, app: &mut App, ) { - app.add_systems( - Update, - ( - attach_joints, - attach_collider_shapes, - attach_spring_roots, - init_spring_joint_states, - ), - ); + app.register_type::() + .register_type::() + .register_type::() + .add_systems( + Update, + ( + attach_joint_props, + attach_collider_shapes, + attach_spring_roots, + init_spring_joint_states, + ), + ); } } -fn attach_joints( +#[derive( + Component, Default, Debug, Copy, Clone, Eq, PartialEq, Hash, Reflect, Serialize, Deserialize, +)] +#[reflect(Component, Serialize, Deserialize, Default)] +struct AttachedJointProps; + +#[derive( + Component, Default, Debug, Copy, Clone, Eq, PartialEq, Hash, Reflect, Serialize, Deserialize, +)] +#[reflect(Component, Serialize, Deserialize, Default)] +struct AttachedColliderShapes; + +#[derive( + Component, Default, Debug, Copy, Clone, Eq, PartialEq, Hash, Reflect, Serialize, Deserialize, +)] +#[reflect(Component, Serialize, Deserialize, Default)] +struct AttachedSpringRoots; + +fn attach_joint_props( par_commands: ParallelCommands, child_searcher: ChildSearcher, - mascots: Query<(Entity, &SpringJointRegistry), Added>, + mascots: Query<(Entity, &SpringJointPropsRegistry), Without>, ) { mascots.par_iter().for_each(|(entity, nodes)| { + if child_searcher.has_not_root_bone(entity) { + return; + } + for (name, props) in nodes.iter() { let Some(joint_entity) = child_searcher.find_from_name(entity, name.as_str()) else { continue; @@ -40,15 +66,21 @@ fn attach_joints( commands.entity(joint_entity).insert(*props); }); } + par_commands.command_scope(|mut commands| { + commands.entity(entity).insert(AttachedJointProps); + }); }); } fn attach_collider_shapes( par_commands: ParallelCommands, child_searcher: ChildSearcher, - mascots: Query<(Entity, &SpringColliderRegistry), Added>, + vrm: Query<(Entity, &SpringColliderRegistry), Without>, ) { - mascots.par_iter().for_each(|(entity, nodes)| { + vrm.par_iter().for_each(|(entity, nodes)| { + if child_searcher.has_not_root_bone(entity) { + return; + } for (name, shape) in nodes.iter() { let Some(collider_entity) = child_searcher.find_from_name(entity, name) else { continue; @@ -57,15 +89,22 @@ fn attach_collider_shapes( commands.entity(collider_entity).insert(*shape); }); } + par_commands.command_scope(|mut commands| { + commands.entity(entity).insert(AttachedColliderShapes); + }); }); } fn attach_spring_roots( par_commands: ParallelCommands, child_searcher: ChildSearcher, - mascots: Query<(Entity, &SpringNodeRegistry), Added>, + mascots: Query<(Entity, &SpringNodeRegistry), Without>, ) { mascots.par_iter().for_each(|(entity, registry)| { + if child_searcher.has_not_root_bone(entity) { + return; + } + for spring_root in registry.0.iter().map(|spring| SpringRoot { center_node: spring .center @@ -90,6 +129,9 @@ fn attach_spring_roots( commands.entity(root).insert(spring_root); }); } + par_commands.command_scope(|mut commands| { + commands.entity(entity).insert(AttachedSpringRoots); + }); }); } @@ -124,9 +166,16 @@ fn init_spring_joint_states( mod tests { use crate::success; use crate::tests::{test_app, TestResult}; - use crate::vrm::spring_bone::attach::{attach_spring_roots, init_spring_joint_states}; - use crate::vrm::spring_bone::registry::{SpringNode, SpringNodeRegistry}; - use crate::vrm::spring_bone::{SpringJointState, SpringRoot}; + use crate::vrm::extensions::vrmc_spring_bone::ColliderShape; + use crate::vrm::spring_bone::attach::{ + attach_collider_shapes, attach_joint_props, attach_spring_roots, init_spring_joint_states, + AttachedColliderShapes, AttachedJointProps, AttachedSpringRoots, + }; + use crate::vrm::spring_bone::registry::{ + SpringColliderRegistry, SpringJointPropsRegistry, SpringNode, SpringNodeRegistry, + }; + use crate::vrm::spring_bone::{SpringJointProps, SpringJointState, SpringRoot}; + use bevy::app::App; use bevy::core::Name; use bevy::ecs::system::RunSystemOnce; use bevy::math::Vec3; @@ -144,6 +193,7 @@ mod tests { joints: vec![Name::new("head")], ..default() }])) + .with_child(Name::new("Root")) .add_child(head); head })?; @@ -184,6 +234,7 @@ mod tests { joints: vec![Name::new("head")], ..default() }])) + .with_child(Name::new("Root")) .add_child(head) .add_child(center); (center, head) @@ -226,6 +277,7 @@ mod tests { joints: vec![Name::new("head"), Name::new("tail")], ..default() }])) + .with_child(Name::new("Root")) .add_child(head) .with_child((Name::new("tail"), Transform::from_xyz(0.0, 2.0, 0.0))); head @@ -260,4 +312,75 @@ mod tests { ); success!() } + + #[test] + fn has_been_attached_joint_props() -> TestResult { + let mut app = test_app(); + spawn_registry(&mut app)?; + + app.world_mut().run_system_once(attach_joint_props)?; + assert!(app + .world_mut() + .query::<&AttachedJointProps>() + .get_single(app.world()) + .is_ok()); + + Ok(()) + } + + #[test] + fn has_been_attached_collider_shapes() -> TestResult { + let mut app = test_app(); + spawn_registry(&mut app)?; + + app.world_mut().run_system_once(attach_collider_shapes)?; + assert!(app + .world_mut() + .query::<&AttachedColliderShapes>() + .get_single(app.world()) + .is_ok()); + + Ok(()) + } + + #[test] + fn has_been_attached_spring_roots() -> TestResult { + let mut app = test_app(); + spawn_registry(&mut app)?; + + app.world_mut().run_system_once(attach_spring_roots)?; + assert!(app + .world_mut() + .query::<&AttachedSpringRoots>() + .get_single(app.world()) + .is_ok()); + + Ok(()) + } + + fn spawn_registry(app: &mut App) -> TestResult { + app.world_mut().run_system_once(|mut commands: Commands| { + commands + .spawn(( + SpringNodeRegistry(vec![SpringNode { + center: None, + joints: vec![Name::new("head")], + ..default() + }]), + SpringColliderRegistry( + [(Name::new("head"), ColliderShape::default())] + .into_iter() + .collect(), + ), + SpringJointPropsRegistry( + [(Name::new("head"), SpringJointProps::default())] + .into_iter() + .collect(), + ), + )) + .with_child(Name::new("Root")) + .with_child(Name::new("head")); + })?; + Ok(()) + } } diff --git a/src/vrm/spring_bone/registry.rs b/src/vrm/spring_bone/registry.rs index 6802303..f450ad7 100644 --- a/src/vrm/spring_bone/registry.rs +++ b/src/vrm/spring_bone/registry.rs @@ -4,11 +4,10 @@ use crate::vrm::extensions::vrmc_spring_bone::{ use crate::vrm::spring_bone::SpringJointProps; use bevy::app::App; use bevy::asset::{Assets, Handle}; -use bevy::core::Name; use bevy::gltf::GltfNode; use bevy::math::Vec3; +use bevy::platform_support::collections::HashMap; use bevy::prelude::*; -use bevy::utils::HashMap; pub struct SpringBoneRegistryPlugin; @@ -18,14 +17,14 @@ impl Plugin for SpringBoneRegistryPlugin { app: &mut App, ) { app.register_type::() - .register_type::() + .register_type::() .register_type::(); } } #[derive(Component, Deref, Reflect, PartialEq, Clone)] #[reflect(Component)] -pub struct SpringColliderRegistry(HashMap); +pub struct SpringColliderRegistry(pub(crate) HashMap); impl SpringColliderRegistry { pub fn new( @@ -47,9 +46,9 @@ impl SpringColliderRegistry { } #[derive(Component, Deref, Reflect)] -pub struct SpringJointRegistry(HashMap); +pub struct SpringJointPropsRegistry(pub(crate) HashMap); -impl SpringJointRegistry { +impl SpringJointPropsRegistry { pub fn new( joints: &[SpringJoint], node_assets: &Assets, diff --git a/src/vrm/spring_bone/update.rs b/src/vrm/spring_bone/update.rs index 953e23a..d2ae94d 100644 --- a/src/vrm/spring_bone/update.rs +++ b/src/vrm/spring_bone/update.rs @@ -1,11 +1,13 @@ use crate::vrm::extensions::vrmc_spring_bone::ColliderShape; use crate::vrm::spring_bone::{SpringJointProps, SpringJointState, SpringRoot}; -use bevy::app::App; +use bevy::app::{App, PostUpdate}; +use bevy::ecs::relationship::Relationship; use bevy::math::Vec3; use bevy::prelude::{ - Entity, GlobalTransform, Parent, Plugin, PostUpdate, Quat, Query, Res, Transform, Without, + ChildOf, Entity, GlobalTransform, Plugin, Quat, Query, Res, Transform, Without, }; use bevy::time::Time; +use std::ops::Mul; pub struct SpringBoneUpdatePlugin; @@ -20,19 +22,19 @@ impl Plugin for SpringBoneUpdatePlugin { fn update_spring_bones( mut transforms: Query<(&mut Transform, &mut GlobalTransform)>, - mut joints: Query<(&Parent, &mut SpringJointState, &SpringJointProps), Without>, + mut joints: Query<(&ChildOf, &mut SpringJointState, &SpringJointProps), Without>, spring_roots: Query<&SpringRoot>, colliders: Query<&ColliderShape>, time: Res