bevy_vrm1 0.2.0

Allows you to use VRM and VRMA in Bevy
Documentation
//! This module inserts [`SceneRoot`] and VRMA-related components from the loaded [`VrmaHandle`].

use crate::vrm::humanoid_bone::{HumanoidBoneRegistry, HumanoidBonesAttached};
use crate::vrma::animation::{VrmAnimationGraph, VrmaAnimationPlayers};
use crate::vrma::gltf::extensions::VrmaExtensions;
use crate::vrma::loader::VrmaAsset;
use crate::vrma::retarget::VrmaExpressionNames;
use crate::vrma::{RetargetTo, Vrma, VrmaDuration, VrmaHandle, VrmaPath};
use bevy::gltf::GltfNode;
use bevy::prelude::*;
use bevy::scene::SceneRoot;
use std::time::Duration;

pub(super) struct VrmaSpawnPlugin;

impl Plugin for VrmaSpawnPlugin {
    fn build(
        &self,
        app: &mut App,
    ) {
        app.add_systems(Update, spawn_vrma);
    }
}

fn spawn_vrma(
    mut commands: Commands,
    mut animation_graphs: ResMut<Assets<AnimationGraph>>,
    vrma_assets: Res<Assets<VrmaAsset>>,
    node_assets: Res<Assets<GltfNode>>,
    clip_assets: Res<Assets<AnimationClip>>,
    vrma_handles: Query<(Entity, &VrmaHandle, &ChildOf)>,
    complements: Query<Entity, With<HumanoidBonesAttached>>,
    global_transform: Query<&GlobalTransform>,
) {
    for (handle_entity, handle, child_of) in vrma_handles.iter() {
        let vrm_entity = child_of.parent();
        if complements.get(vrm_entity).is_err() {
            continue;
        }
        if !global_transform.contains(vrm_entity) {
            continue;
        }
        let Some(vrma_path) = handle.0.path().map(|path| path.path().to_path_buf()) else {
            continue;
        };
        let Some(name) = handle.0.path().map(|p| p.to_string()) else {
            continue;
        };
        let Some(vrma) = vrma_assets.get(handle.0.id()) else {
            continue;
        };
        commands.entity(handle_entity).remove::<VrmaHandle>();

        let Some(scene_root) = vrma.gltf.scenes.first().cloned() else {
            error!("[VRMA] Not found vrma scene in {name}");
            continue;
        };
        let extensions = match VrmaExtensions::from_gltf(&vrma.gltf) {
            Ok(extensions) => extensions,
            Err(_e) => {
                error!("[VRMA] Not found vrma extensions in {name}:\n{_e}");
                continue;
            }
        };

        commands.entity(handle_entity).insert((
            Vrma,
            Name::new(name),
            VrmaAnimationPlayers::default(),
            RetargetTo(child_of.parent()),
            SceneRoot(scene_root),
            VrmaDuration(obtain_vrma_duration(&clip_assets, &vrma.gltf.animations)),
            VrmaPath(vrma_path),
            VrmAnimationGraph::new(vrma.gltf.animations.to_vec(), &mut animation_graphs),
            VrmaExpressionNames::new(&extensions),
            HumanoidBoneRegistry::new(
                &extensions.vrmc_vrm_animation.humanoid.human_bones,
                &node_assets,
                &vrma.gltf.nodes,
            ),
        ));
    }
}

fn obtain_vrma_duration(
    assets: &Assets<AnimationClip>,
    handles: &[Handle<AnimationClip>],
) -> Duration {
    let duration = handles
        .iter()
        .filter_map(|handle| assets.get(handle))
        .map(|clip| clip.duration() as f64)
        .fold(0., |v1, v2| v2.max(v1));
    Duration::from_secs_f64(duration)
}