diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10Loaded.cs b/Assets/VRM10_Samples/VRM10Viewer/VRM10Loaded.cs index 60dc9e012..9fbde3277 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/VRM10Loaded.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10Loaded.cs @@ -9,6 +9,7 @@ namespace UniVRM10.VRM10Viewer { RuntimeGltfInstance m_instance; Vrm10Instance m_controller; + public Vrm10RuntimeControlRig ControlRig => m_controller.Runtime.ControlRig; VRM10AIUEO m_lipSync; bool m_enableLipSyncValue; @@ -88,110 +89,6 @@ namespace UniVRM10.VRM10Viewer GameObject.Destroy(m_instance.gameObject); } - /// - /// from v0.103 - /// - /// - public void UpdateControlRigExplicit(Animator src) - { - var controlRig = m_controller.Runtime.ControlRig; - - foreach (HumanBodyBones bone in CachedEnum.GetValues()) - { - if (bone == HumanBodyBones.LastBone) - { - continue; - } - - var controlRigBone = controlRig.GetBoneTransform(bone); - if (controlRigBone == null) - { - continue; - } - - var bvhBone = src.GetBoneTransform(bone); - if (bvhBone != null) - { - // set normalized pose - controlRigBone.localRotation = bvhBone.localRotation; - } - - if (bone == HumanBodyBones.Hips) - { - controlRigBone.localPosition = bvhBone.localPosition * controlRig.InitialHipsHeight; - } - } - } - - /// - /// from v0.104 - /// - public void UpdateControlRigImplicit(Animator src) - { - var dst = m_controller.GetComponent(); - - foreach (HumanBodyBones bone in CachedEnum.GetValues()) - { - if (bone == HumanBodyBones.LastBone) - { - continue; - } - - var boneTransform = dst.GetBoneTransform(bone); - if (boneTransform == null) - { - continue; - } - - var bvhBone = src.GetBoneTransform(bone); - if (bvhBone != null) - { - // set normalized pose - boneTransform.localRotation = bvhBone.localRotation; - } - - if (bone == HumanBodyBones.Hips) - { - // TODO: hips position scaling ? - boneTransform.localPosition = bvhBone.localPosition; - } - } - } - - /// - /// from v0.108 - /// - public void UpdateControlRigImplicit(UniHumanoid.Humanoid src) - { - var dst = m_controller.GetComponent(); - - foreach (HumanBodyBones bone in CachedEnum.GetValues()) - { - if (bone == HumanBodyBones.LastBone) - { - continue; - } - - var boneTransform = dst.GetBoneTransform(bone); - if (boneTransform == null) - { - continue; - } - - var bvhBone = src.GetBoneTransform(bone); - if (bvhBone != null) - { - // set normalized pose - boneTransform.localRotation = bvhBone.localRotation; - if (bone == HumanBodyBones.Hips) - { - // TODO: hips position scaling ? - boneTransform.localPosition = bvhBone.localPosition; - } - } - } - } - public void TPoseControlRig() { var controlRig = m_controller.Runtime.ControlRig; diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10Motion.cs b/Assets/VRM10_Samples/VRM10Viewer/VRM10Motion.cs new file mode 100644 index 000000000..08107b32b --- /dev/null +++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10Motion.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using UniGLTF; +using UniHumanoid; +using UniJSON; +using UnityEngine; +using VRMShaders; + +namespace UniVRM10.VRM10Viewer +{ + public class VRM10Motion + { + public (INormalizedPoseProvider, ITPoseProvider) ControlRig; + + UniHumanoid.BvhImporterContext m_context; + UniGLTF.RuntimeGltfInstance m_instance; + + public Transform Root => m_context?.Root.transform; + + public VRM10Motion(UniHumanoid.BvhImporterContext context) + { + m_context = context; + var provider = new NormalizedPoseProvider(m_context.Root.transform, m_context.Root.GetComponent()); + ControlRig = (provider, provider); + } + + public VRM10Motion(UniGLTF.RuntimeGltfInstance instance) + { + m_instance = instance; + if (instance.GetComponent() is Animation animation) + { + animation.Play(); + } + } + + public void ShowBoxMan(bool showBoxMan) + { + if (m_context != null) + { + m_context.Root.GetComponent().enabled = showBoxMan; + } + } + + public static VRM10Motion LoadBvhFromText(string source, string path = "tmp.bvh") + { + var context = new UniHumanoid.BvhImporterContext(); + context.Parse(path, source); + context.Load(); + return new VRM10Motion(context); + } + + public static VRM10Motion LoadBvhFromPath(string path) + { + return LoadBvhFromText(File.ReadAllText(path), path); + } + + static IEnumerable Traverse(Transform t) + { + yield return t; + foreach (Transform child in t) + { + foreach (var x in Traverse(child)) + { + yield return x; + } + } + } + + #region experimental vrm-animation deserializer + // + // vrm-animation の簡易実装 + // + // https://github.com/vrm-c/vrm-animation + // + static float ForceMeterScale(Dictionary map) + { + var positionMap = map.ToDictionary(kv => kv.Key, kv => kv.Value.position); + var hipsHeight = positionMap[HumanBodyBones.Hips].y; + + float scaling = 1.0f; + if (hipsHeight > 80) + { + // cm スケールであると見做す + scaling = 0.01f; + } + return scaling; + } + + static bool TryGet(JsonNode obj, string key, out JsonNode found) + { + foreach (var kv in obj.ObjectItems()) + { + if (kv.Key.GetString() == key) + { + found = kv.Value; + return true; + } + } + found = default; + return false; + } + + static (HumanBodyBones, int) ToTuple(KeyValuePair kv) + { + if (TryGet(kv.Value, "node", out var value)) + { + var name = kv.Key.GetString(); + switch (name) + { + case "rightThumbMetacarpal": + return (HumanBodyBones.RightThumbProximal, value.GetInt32()); + case "leftThumbMetacarpal": + return (HumanBodyBones.LeftThumbProximal, value.GetInt32()); + case "rightThumbProximal": + return (HumanBodyBones.RightThumbIntermediate, value.GetInt32()); + case "leftThumbProximal": + return (HumanBodyBones.LeftThumbIntermediate, value.GetInt32()); + default: + return ((HumanBodyBones)Enum.Parse(typeof(HumanBodyBones), name, true), value.GetInt32()); + } + } + throw new Exception(); + } + + static Dictionary GetHumanMap(GltfData data, IReadOnlyList nodes) + { + var humanMap = new Dictionary(); + if (data.GLTF.extensions is UniGLTF.glTFExtensionImport extensions) + { + foreach (var kv in extensions.ObjectItems()) + { + if (kv.Key.GetString() == "VRMC_vrm_animation") + { + var animation = kv.Value; + if (TryGet(animation, "humanoid", out var animation_humanoid)) + { + if (TryGet(animation_humanoid, "humanBones", out var bones)) + { + foreach (var kkvv in bones.ObjectItems()) + { + var (bone, index) = ToTuple(kkvv); + // Debug.Log($"{bone} => {index}"); + humanMap.Add(bone, nodes[index]); + } + } + } + } + } + } + return humanMap; + } + #endregion + + public static async Task LoadVrmAnimationFromPathAsync(string path) + { + // + // GetHumanoid Mapping + // + using (GltfData data = new AutoGltfFileParser(path).Parse()) + using (var loader = new UniGLTF.ImporterContext(data)) + { + loader.InvertAxis = Axes.X; + // loader.PositionScaling = 0.01f; + var instance = await loader.LoadAsync(new ImmediateCaller()); + var humanMap = GetHumanMap(data, loader.Nodes); + if (humanMap.Count == 0) + { + throw new ArgumentException("fail to load VRMC_vrm_animation"); + } + + var scaling = ForceMeterScale(humanMap); + // instance.transform.localScale = new Vector3(scaling, scaling, scaling); + var description = AvatarDescription.Create(humanMap); + + // + // avatar + // + var avatar = description.CreateAvatar(instance.Root.transform); + avatar.name = "Avatar"; + // AvatarDescription = description; + var animator = instance.gameObject.AddComponent(); + animator.avatar = avatar; + + // create SkinnedMesh for bone visualize + var renderer = SkeletonMeshUtility.CreateRenderer(animator); + var material = new Material(Shader.Find("Standard")); + renderer.sharedMaterial = material; + var mesh = renderer.sharedMesh; + mesh.name = "box-man"; + + var humanoid = instance.gameObject.AddComponent(); + humanoid.AssignBonesFromAnimator(); + var motion = new VRM10Motion(instance); + var poseProvider = new InitRotationPoseProvider(instance.transform, humanoid); + motion.ControlRig = (poseProvider, poseProvider); + return motion; + } + } + + public void Retarget(Vrm10RuntimeControlRig dst) + { + VRM10Retarget.Retarget(ControlRig, (dst, dst)); + } + } +} diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10Motion.cs.meta b/Assets/VRM10_Samples/VRM10Viewer/VRM10Motion.cs.meta new file mode 100644 index 000000000..a3c573986 --- /dev/null +++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10Motion.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 08f06b12436bf594b9982c6a8ec71374 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs b/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs index e73e89caf..42424e86d 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; using System.Threading; using UniGLTF; -using UniHumanoid; using UnityEngine; using UnityEngine.UI; using VRMShaders; @@ -34,18 +33,15 @@ namespace UniVRM10.VRM10Viewer [SerializeField] Toggle m_useAsync = default; - [Header("Runtime")] - [SerializeField] - Animator m_src = default; - [SerializeField] GameObject m_target = default; [SerializeField] + TextAsset m_motion; + GameObject Root = default; - [SerializeField] - TextAsset m_motion; + VRM10Motion m_src = default; private CancellationTokenSource _cancellationTokenSource; @@ -181,8 +177,6 @@ namespace UniVRM10.VRM10Viewer var texts = GameObject.FindObjectsOfType(); m_version = texts.First(x => x.name == "Version"); - m_src = GameObject.FindObjectOfType(); - m_target = GameObject.FindObjectOfType().gameObject; } @@ -197,7 +191,7 @@ namespace UniVRM10.VRM10Viewer // load initial bvh if (m_motion != null) { - LoadMotion(m_motion.text); + m_src = VRM10Motion.LoadBvhFromText(m_motion.text); } string[] cmds = System.Environment.GetCommandLineArgs(); @@ -217,17 +211,6 @@ namespace UniVRM10.VRM10Viewer _cancellationTokenSource?.Dispose(); } - private void LoadMotion(string source) - { - var context = new UniHumanoid.BvhImporterContext(); - context.Parse("tmp.bvh", File.ReadAllText(source)); - context.Load(); - m_src = context.Root.GetComponent(); - m_ui.IsBvhEnabled = true; - // hide box man - context.Root.GetComponent().enabled = false; - } - private void Update() { if (m_loaded != null) @@ -254,7 +237,7 @@ namespace UniVRM10.VRM10Viewer { if (m_ui.IsBvhEnabled && m_src != null) { - m_loaded.UpdateControlRigImplicit(m_src); + m_src.Retarget(m_loaded.ControlRig); } else { @@ -280,7 +263,7 @@ namespace UniVRM10.VRM10Viewer var ext = Path.GetExtension(path).ToLower(); if (ext == ".bvh") { - LoadMotion(path); + m_src = VRM10Motion.LoadBvhFromPath(path); return; }