bevy_vrm1 0.6.1

Allows you to use VRM and VRMA in Bevy
Documentation
use crate::vrm::humanoid_bone::HumanoidBoneRegistry;
use crate::vrm::{Vrm, VrmBone};
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;

#[derive(SystemParam)]
pub struct ChildSearcher<'w, 's> {
    entities: Query<
        'w,
        's,
        (
            Option<&'static Name>,
            Option<&'static VrmBone>,
            Option<&'static Children>,
        ),
    >,
}

impl ChildSearcher<'_, '_> {
    #[inline]
    pub fn find_root_bone(
        &self,
        vrm: Entity,
    ) -> Option<Entity> {
        self.find_from_name(vrm, Vrm::ROOT_BONE)
    }

    #[inline]
    pub fn find_expressions_root(
        &self,
        vrm: Entity,
    ) -> Option<Entity> {
        self.find_from_name(vrm, Vrm::EXPRESSIONS_ROOT)
    }

    pub fn find_from_name(
        &self,
        root: Entity,
        target_name: &str,
    ) -> Option<Entity> {
        find_entity(target_name, false, root, &self.entities)
    }

    pub fn find_by_bone_name(
        &self,
        root: Entity,
        target_name: &VrmBone,
    ) -> Option<Entity> {
        find_entity(target_name, true, root, &self.entities)
    }

    pub(crate) fn has_been_spawned_all_bones(
        &self,
        root: Entity,
        bone_registry: &HumanoidBoneRegistry,
    ) -> bool {
        bone_registry
            .values()
            .all(|bone_name| self.find_from_name(root, bone_name.as_str()).is_some())
    }
}

fn find_entity(
    target_name: &str,
    is_bone: bool,
    entity: Entity,
    entities: &Query<(Option<&Name>, Option<&VrmBone>, Option<&Children>)>,
) -> Option<Entity> {
    let (name, bone, children) = entities.get(entity).ok()?;
    if is_bone {
        if bone.is_some_and(|bone| bone.0 == target_name) {
            return Some(entity);
        }
    } else if name.is_some_and(|name| name.as_str() == target_name) {
        return Some(entity);
    }

    for child in children? {
        if let Some(entity) = find_entity(target_name, is_bone, *child, entities) {
            return Some(entity);
        }
    }
    None
}

#[cfg(test)]
mod tests {
    use crate::prelude::*;
    use crate::tests::test_app;
    use bevy::prelude::*;
    use bevy_test_helper::system::SystemExt;

    #[test]
    fn test_find_root_bone() {
        let mut app = test_app();

        let vrm = app.world_mut().spawn_empty().id();
        app.world_mut()
            .commands()
            .entity(vrm)
            .with_child(Name::new(Vrm::ROOT_BONE));
        app.update();

        app.run_system_once(move |s: ChildSearcher| s.find_root_bone(vrm))
            .expect("Failed to find root bone");
    }
}