bevy_vrm1 0.3.0

Allows you to use VRM and VRMA in Bevy
Documentation
use crate::error::vrm_error;
use crate::prelude::ChildSearcher;
use crate::vrm::expressions::{RequestInitializeExpressions, VrmExpressionRegistry};
use crate::vrm::gltf::extensions::VrmExtensions;
use crate::vrm::humanoid_bone::{HumanoidBoneRegistry, RequestInitializeHumanoidBones};
use crate::vrm::loader::{VrmAsset, VrmHandle};
use crate::vrm::mtoon::VrmcMaterialRegistry;
use crate::vrm::node_constraint::initialize::RequestInitializeNodeConstraints;
use crate::vrm::node_constraint::registry::NodeConstraintRegistry;
use crate::vrm::spring_bone::initialize::RequestInitializeSpringBone;
use crate::vrm::spring_bone::registry::*;
use crate::vrm::{Initialized, Vrm, VrmPath};
use crate::vrma::Vrma;
use crate::vrma::animation::animation_graph::RequestUpdateAnimationGraph;
use bevy::app::{App, Update};
use bevy::asset::Assets;
use bevy::gltf::GltfNode;
use bevy::prelude::*;
use bevy::scene::SceneRoot;

pub(crate) struct VrmInitializePlugin;

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

fn spawn_vrm(
    mut commands: Commands,
    node_assets: Res<Assets<GltfNode>>,
    vrm_assets: Res<Assets<VrmAsset>>,
    handles: Query<(Entity, &VrmHandle)>,
) {
    for (vrm_handle_entity, handle) in handles.iter() {
        let Some(vrm) = vrm_assets.get(handle.0.id()) else {
            continue;
        };
        commands.entity(vrm_handle_entity).remove::<VrmHandle>();

        let Some(scene) = vrm.gltf.scenes.first() else {
            continue;
        };
        let extensions = match VrmExtensions::from_gltf(&vrm.gltf) {
            Ok(extensions) => extensions,
            Err(e) => {
                vrm_error!("Failed to load VRM extensions", e);
                continue;
            }
        };
        let mut cmd = commands.entity(vrm_handle_entity);
        cmd.insert((
            Vrm,
            Name::new(extensions.name().unwrap_or_else(|| "VRM".to_string())),
            SceneRoot(scene.clone()),
            VrmcMaterialRegistry::new(&vrm.gltf, vrm.images.clone()),
            VrmExpressionRegistry::new(&extensions, &node_assets, &vrm.gltf.nodes),
            HumanoidBoneRegistry::new(
                &extensions.vrmc_vrm.humanoid.human_bones,
                &node_assets,
                &vrm.gltf.nodes,
            ),
            NodeConstraintRegistry::new(&vrm.gltf, &node_assets),
        ));

        if let Some(spring_bone) = extensions.vrmc_spring_bone.as_ref() {
            cmd.insert((
                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),
            ));
        }

        if let Some(look_at) = extensions.vrmc_vrm.look_at.clone() {
            cmd.insert(look_at);
        }

        if let Some(vrm_path) = handle.0.path() {
            #[cfg(feature = "develop")]
            {
                if let Some(vrm_name) = vrm_path.path().file_stem() {
                    let _ = std::fs::create_dir_all("./develop");
                    output_vrm(vrm_name, &vrm.gltf);
                    output_vrm_materials(vrm_name, &vrm.gltf);
                    output_vrm_extensions(vrm_name, &extensions);
                }
            }
            cmd.insert(VrmPath::new(vrm_path.path()));
        }
    }
}

fn request_initialize(
    mut commands: Commands,
    models: Query<(Entity, &HumanoidBoneRegistry, Has<Vrma>), Without<Initialized>>,
    parents: Query<&ChildOf>,
    searcher: ChildSearcher,
) {
    for (root, registry, has_vrma) in models.iter() {
        if !searcher.has_been_spawned_all_bones(root, registry) {
            continue;
        }
        commands
            .entity(root)
            .trigger(RequestInitializeHumanoidBones)
            .trigger(RequestInitializeSpringBone)
            .trigger(RequestInitializeNodeConstraints);
        if has_vrma {
            if let Ok(ChildOf(vrm)) = parents.get(root) {
                commands.entity(root).trigger(RequestUpdateAnimationGraph {
                    vrma: root,
                    vrm: *vrm,
                });
            };
        } else {
            commands.entity(root).trigger(RequestInitializeExpressions);
        }
        commands.entity(root).insert(Initialized);
    }
}

#[cfg(feature = "develop")]
fn output_vrm(
    vrm_name: &std::ffi::OsStr,
    gltf: &Gltf,
) {
    let name = vrm_name.to_str().unwrap();
    let _ = std::fs::create_dir_all("./develop");
    std::fs::write(
        format!("./develop/{name}.json"),
        serde_json::to_string_pretty(&gltf.source.as_ref().unwrap().as_json()).unwrap(),
    )
    .unwrap();
}

#[cfg(feature = "develop")]
fn output_vrm_materials(
    vrm_name: &std::ffi::OsStr,
    gltf: &Gltf,
) {
    let name = vrm_name.to_str().unwrap();
    std::fs::write(
        format!("./develop/{name}_materials.json"),
        serde_json::to_string_pretty(&gltf.source.as_ref().unwrap().as_json().materials).unwrap(),
    )
    .unwrap();
}

#[cfg(feature = "develop")]
fn output_vrm_extensions(
    vrm_name: &std::ffi::OsStr,
    extensions: &VrmExtensions,
) {
    let name = vrm_name.to_str().unwrap();
    std::fs::write(
        format!("./develop/{name}_extensions.json"),
        serde_json::to_string_pretty(extensions).unwrap(),
    )
    .unwrap();
}