128 lines
4.4 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using UniGLTF;
using UniHumanoid;
using UnityEditor;
using UnityEngine;
namespace UniVRM10
{
internal static class VrmAnimationMenu
{
public const string MENU_NAME = "Convert BVH to VRM-Animation...";
public static void BvhToVrmAnimationMenu()
{
var path = EditorUtility.OpenFilePanel("select bvh", null, "bvh");
if (!string.IsNullOrEmpty(path))
{
var bytes = BvhToVrmAnimation(path);
var dst = EditorUtility.SaveFilePanel("write vrma",
Path.GetDirectoryName(path),
Path.GetFileNameWithoutExtension(path),
"vrma");
if (!string.IsNullOrEmpty(dst))
{
File.WriteAllBytes(dst, bytes);
}
}
}
static Transform GetParentBone(Dictionary<HumanBodyBones, Transform> map, Vrm10HumanoidBones bone)
{
while (true)
{
if (bone == Vrm10HumanoidBones.Hips)
{
break;
}
var parentBone = Vrm10HumanoidBoneSpecification.GetDefine(bone).ParentBone.Value;
var unityParentBone = Vrm10HumanoidBoneSpecification.ConvertToUnityBone(parentBone);
if (map.TryGetValue(unityParentBone, out var found))
{
return found;
}
bone = parentBone;
}
// hips has no parent
return null;
}
/// 使用する BVH は次の条件を満たす必要があります。
///
/// * レストポーズが TPose であること
///
/// また、BVH には HumanBone の割り当てが記述されておらず、
/// 大きさに関しても単位の規定がありません。
///
/// bvh.Load 関数の中で HumanBone の名前ベースの割り当て処理と、
/// メートルサイズへのスケーリング(cm to meter など)をしています。
///
/// bvh のボーン割り当てを追加する場合は、
/// bvh.Parse 内の Skeleton.Estimate 関数を参照してください。
///
static byte[] BvhToVrmAnimation(string path)
{
var bvh = new BvhImporterContext();
bvh.Parse(path, File.ReadAllText(path));
bvh.Load();
var data = new ExportingGltfData();
using var exporter = new VrmAnimationExporter(
data, new GltfExportSettings());
exporter.Prepare(bvh.Root.gameObject);
exporter.Export((VrmAnimationExporter vrma) =>
{
//
// setup
//
var map = new Dictionary<HumanBodyBones, Transform>();
var animator = bvh.Root.GetComponent<Animator>();
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
{
if (bone == HumanBodyBones.LastBone)
{
continue;
}
var t = animator.GetBoneTransform(bone);
if (t == null)
{
continue;
}
map.Add(bone, t);
}
vrma.SetPositionBoneAndParent(map[HumanBodyBones.Hips], bvh.Root.transform);
foreach (var kv in map)
{
var vrmBone = Vrm10HumanoidBoneSpecification.ConvertFromUnityBone(kv.Key);
var parent = GetParentBone(map, vrmBone) ?? bvh.Root.transform;
vrma.AddRotationBoneAndParent(kv.Key, kv.Value, parent);
}
//
// get data
//
var animation = bvh.Root.gameObject.GetComponent<Animation>();
var clip = animation.clip;
var state = animation[clip.name];
var time = default(TimeSpan);
for (int i = 0; i < bvh.Bvh.FrameCount; ++i, time += bvh.Bvh.FrameTime)
{
state.time = (float)time.TotalSeconds;
animation.Sample();
vrma.AddFrame(time);
}
});
return data.ToGlbBytes();
}
}
}