bevy_vrm1 0.1.2

Allows you to use VRM and VRMA in Bevy
Documentation
use crate::vrm::gltf::extensions::vrmc_spring_bone::ColliderShape;
use crate::vrm::spring_bone::{SpringJointProps, SpringJointState, SpringRoot};
use bevy::app::App;
use bevy::math::Vec3;
use bevy::prelude::*;
use bevy::time::Time;

pub struct SpringBoneUpdatePlugin;

impl Plugin for SpringBoneUpdatePlugin {
    fn build(
        &self,
        app: &mut App,
    ) {
        app.add_systems(PostUpdate, update_spring_bones);
    }
}

fn update_spring_bones(
    mut transforms: Query<(&mut Transform, &mut GlobalTransform)>,
    mut joints: Query<(&ChildOf, &mut SpringJointState, &SpringJointProps)>,
    spring_roots: Query<&SpringRoot>,
    colliders: Query<&ColliderShape>,
    time: Res<Time>,
) {
    let delta_time = time.delta_secs();
    for spring_root in spring_roots.iter() {
        for joint in spring_root.joints.iter().copied() {
            let Ok((child_of, mut state, props)) = joints.get_mut(joint) else {
                continue;
            };
            let parent_gtf = transforms
                .get(child_of.parent())
                .map(|(_, gtf)| *gtf)
                .unwrap_or_default();
            let parent_global_rotation = parent_gtf.to_scale_rotation_translation().1;
            let Ok(head_global_pos) = transforms.get(joint).map(|(_, gtf)| gtf.translation())
            else {
                continue;
            };

            let inertia = (state.current_tail - state.prev_tail) * (1. - props.drag_force);
            let stiffness = delta_time
                * (parent_global_rotation
                    * state.initial_local_rotation
                    * state.bone_axis
                    * props.stiffness);
            let external = delta_time * props.gravity_dir * props.gravity_power;

            let next_tail = state.current_tail + inertia + stiffness + external;
            let mut next_tail =
                head_global_pos + (next_tail - head_global_pos).normalize() * state.bone_length;

            collision(
                &mut next_tail,
                spring_root.colliders.iter().copied(),
                props.hit_radius,
                head_global_pos,
                state.bone_length,
                &transforms,
                &colliders,
            );

            state.prev_tail = state.current_tail;
            state.current_tail = next_tail;

            let to = (parent_gtf.compute_matrix() * state.initial_local_matrix)
                .inverse()
                .transform_point3(next_tail)
                .normalize();

            let Ok((mut tf, mut gtf)) = transforms.get_mut(joint) else {
                continue;
            };

            tf.rotation =
                state.initial_local_rotation * Quat::from_rotation_arc(state.bone_axis, to);
            *gtf = parent_gtf.mul_transform(*tf);
        }
    }
}

fn collision(
    next_tail: &mut Vec3,
    collider_entities: impl Iterator<Item = Entity>,
    joint_radius: f32,
    head_global_pos: Vec3,
    bone_length: f32,
    transforms: &Query<(&mut Transform, &mut GlobalTransform)>,
    colliders: &Query<&ColliderShape>,
) {
    for collider in collider_entities {
        let Ok(collider_shape) = colliders.get(collider) else {
            continue;
        };
        let Ok((_, collider_gtf)) = transforms.get(collider) else {
            continue;
        };
        collider_shape.calc_collision(
            next_tail,
            collider_gtf,
            head_global_pos,
            joint_radius,
            bone_length,
        );
    }
}