diff --git a/Assets/UniGLTF/Runtime/UniHumanoid/Format/Bvh.cs b/Assets/UniGLTF/Runtime/UniHumanoid/Format/Bvh.cs index 1f8b9126e..0310dc6ac 100644 --- a/Assets/UniGLTF/Runtime/UniHumanoid/Format/Bvh.cs +++ b/Assets/UniGLTF/Runtime/UniHumanoid/Format/Bvh.cs @@ -99,6 +99,18 @@ namespace UniHumanoid Children = new List(); } + public int GetChannelIndex(Channel channel) + { + for (int i = 0; i < Channels.Length; ++i) + { + if (channel == Channels[i]) + { + return i; + } + } + throw new KeyNotFoundException(); + } + public virtual void Parse(StringReader r) { Offset = ParseOffset(r.ReadLine()); diff --git a/Assets/UniGLTF/Runtime/UniHumanoid/IO/BvhImporterContext.cs b/Assets/UniGLTF/Runtime/UniHumanoid/IO/BvhImporterContext.cs index 378810f81..572320560 100644 --- a/Assets/UniGLTF/Runtime/UniHumanoid/IO/BvhImporterContext.cs +++ b/Assets/UniGLTF/Runtime/UniHumanoid/IO/BvhImporterContext.cs @@ -75,11 +75,15 @@ namespace UniHumanoid // // scaling. reposition // + var yCh = Bvh.Root.GetChannelIndex(Channel.Yposition); + var curve = Bvh.Channels[yCh]; + var hipHeight = curve.Keys[0]; + float scaling = 1.0f; { //var foot = animator.GetBoneTransform(HumanBodyBones.LeftFoot); - var foot = hips.Traverse().Skip(skeleton.GetBoneIndex(HumanBodyBones.LeftFoot)).First(); - var hipHeight = hips.position.y - foot.position.y; + // var foot = hips.Traverse().Skip(skeleton.GetBoneIndex(HumanBodyBones.LeftFoot)).First(); + // var hipHeight = hips.position.y - foot.position.y; // hips height to a meter scaling = 1.0f / hipHeight; foreach (var x in Root.transform.Traverse()) diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/ControlRig/Vrm10ControlBone.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/ControlRig/Vrm10ControlBone.cs index eec7aa06a..80c41055a 100644 --- a/Assets/VRM10/Runtime/Components/Vrm10Runtime/ControlRig/Vrm10ControlBone.cs +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/ControlRig/Vrm10ControlBone.cs @@ -33,10 +33,11 @@ namespace UniVRM10 private readonly Quaternion _initialTargetLocalRotation; private readonly Quaternion _initialTargetGlobalRotation; + public Vector3 InitialTargetGlobalPosition { get; } private readonly List _children = new List(); public IReadOnlyList Children => _children; - private Vrm10ControlBone(Transform controlTarget, HumanBodyBones boneType, Vrm10ControlBone parent) + private Vrm10ControlBone(Transform controlTarget, HumanBodyBones boneType, Vrm10ControlBone parent, Transform root) { if (boneType == HumanBodyBones.LastBone) { @@ -62,6 +63,7 @@ namespace UniVRM10 _initialTargetLocalRotation = controlTarget.localRotation; _initialTargetGlobalRotation = controlTarget.rotation; + InitialTargetGlobalPosition = root.worldToLocalMatrix.MultiplyPoint(controlTarget.position); } /// @@ -76,32 +78,32 @@ namespace UniVRM10 } } - public static Vrm10ControlBone Build(UniHumanoid.Humanoid humanoid, out Dictionary boneMap) + public static Vrm10ControlBone Build(UniHumanoid.Humanoid humanoid, out Dictionary boneMap, Transform root) { - var hips = new Vrm10ControlBone(humanoid.Hips, HumanBodyBones.Hips, null); + var hips = new Vrm10ControlBone(humanoid.Hips, HumanBodyBones.Hips, null, root); boneMap = new Dictionary(); boneMap.Add(HumanBodyBones.Hips, hips); foreach (Transform child in humanoid.Hips) { - BuildRecursively(humanoid, child, hips, boneMap); + BuildRecursively(humanoid, child, hips, boneMap, root); } return hips; } - private static void BuildRecursively(UniHumanoid.Humanoid humanoid, Transform current, Vrm10ControlBone parent, Dictionary boneMap) + private static void BuildRecursively(UniHumanoid.Humanoid humanoid, Transform current, Vrm10ControlBone parent, Dictionary boneMap, Transform root) { if (humanoid.TryGetBoneForTransform(current, out var bone)) { - var newBone = new Vrm10ControlBone(current, bone, parent); + var newBone = new Vrm10ControlBone(current, bone, parent, root); parent = newBone; boneMap.Add(bone, newBone); } foreach (Transform child in current) { - BuildRecursively(humanoid, child, parent, boneMap); + BuildRecursively(humanoid, child, parent, boneMap, root); } } } diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/ControlRig/Vrm10RuntimeControlRig.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/ControlRig/Vrm10RuntimeControlRig.cs index 9abd96c5d..c0d641a58 100644 --- a/Assets/VRM10/Runtime/Components/Vrm10Runtime/ControlRig/Vrm10RuntimeControlRig.cs +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/ControlRig/Vrm10RuntimeControlRig.cs @@ -35,7 +35,7 @@ namespace UniVRM10 _controlRigRoot = new GameObject("Runtime Control Rig").transform; _controlRigRoot.SetParent(vrmRoot); - _hipBone = Vrm10ControlBone.Build(humanoid, out _bones); + _hipBone = Vrm10ControlBone.Build(humanoid, out _bones, _controlRigRoot); _hipBone.ControlBone.SetParent(_controlRigRoot); HipTPoseWorldPosition = vrmRoot.worldToLocalMatrix.MultiplyPoint(_hipBone.ControlTarget.position); @@ -72,19 +72,11 @@ namespace UniVRM10 } } - public void EnforceTPose() - { - SetHipsPosition(_hipBone.ControlBone.position); - foreach (var bone in _bones.Values) - { - bone.ControlBone.localRotation = Quaternion.identity; - } - } - + #region INormalizedPoseApplicable public void SetHipsPosition(Vector3 position) { - // TODO: scale model local position - _hipBone.ControlTarget.position = position; + var world = _controlRigRoot.TransformPoint(position); + _hipBone.ControlBone.position = world; } public void SetNormalizedLocalRotation(HumanBodyBones bone, Quaternion normalizedLocalRotation) @@ -94,6 +86,7 @@ namespace UniVRM10 t.ControlBone.localRotation = normalizedLocalRotation; } } + #endregion IEnumerable<(HumanBodyBones Head, HumanBodyBones Parent)> Traverse(Vrm10ControlBone bone, Vrm10ControlBone parent = null) { @@ -108,6 +101,7 @@ namespace UniVRM10 } } + #region ITPoseProvider public IEnumerable<(HumanBodyBones Head, HumanBodyBones Parent)> EnumerateBones() { foreach (var headParent in Traverse(_hipBone)) @@ -122,5 +116,11 @@ namespace UniVRM10 { return Quaternion.identity; } + + public Vector3 GetBoneTPoseWorldPosition(HumanBodyBones bone) + { + return _bones[bone].InitialTargetGlobalPosition; + } + #endregion } } diff --git a/Assets/VRM10/Runtime/ControlRig/AnimatorPoseProvider.cs b/Assets/VRM10/Runtime/ControlRig/AnimatorPoseProvider.cs new file mode 100644 index 000000000..b29fd971c --- /dev/null +++ b/Assets/VRM10/Runtime/ControlRig/AnimatorPoseProvider.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// Animator から Pose 供給する。 + /// この Animator は以下の条件を満たすこと。 + /// + /// * TPoseであること + /// * 正規化済みであること + /// + /// + public sealed class AnimatorPoseProvider : INormalizedPoseProvider, ITPoseProvider + { + Transform m_root; + Animator m_animator; + Dictionary m_posMap = new Dictionary(); + + public AnimatorPoseProvider(Transform root, Animator animator) + { + m_root = root; + m_animator = animator; + + var hips = animator.GetBoneTransform(HumanBodyBones.Hips); + HipTPoseWorldPosition = root.worldToLocalMatrix.MultiplyPoint(hips.position); + + foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones))) + { + if(bone==HumanBodyBones.LastBone) + { + continue; + } + var t = animator.GetBoneTransform(bone); + if (t != null) + { + m_posMap[bone] = root.worldToLocalMatrix.MultiplyPoint(t.position); + } + } + } + + #region INormalizedPoseProvider + public Quaternion GetNormalizedLocalRotation(HumanBodyBones bone, HumanBodyBones parentBone) + { + if (m_animator.GetBoneTransform(bone) is Transform t) + { + // TODO: parentBone relative + return t.localRotation; + } + else + { + // Debug.LogWarning($"{bone} not found"); + return Quaternion.identity; + } + } + + public Vector3 GetHipsPosition() + { + // TODO: from model root ? + return m_animator.GetBoneTransform(HumanBodyBones.Hips).localPosition; + } + #endregion + + #region ITPoseProvider + public Vector3 HipTPoseWorldPosition { get; } + + public Quaternion GetBoneTPoseWorldRotation(HumanBodyBones bone) + { + throw new System.NotImplementedException(); + } + + public IEnumerable<(HumanBodyBones Head, HumanBodyBones Parent)> EnumerateBones() + { + throw new System.NotImplementedException(); + } + + public Vector3 GetBoneTPoseWorldPosition(HumanBodyBones bone) + { + return m_posMap[bone]; + } + #endregion + } +} diff --git a/Assets/VRM10/Runtime/ControlRig/NormalizedPoseProvider.cs.meta b/Assets/VRM10/Runtime/ControlRig/AnimatorPoseProvider.cs.meta similarity index 100% rename from Assets/VRM10/Runtime/ControlRig/NormalizedPoseProvider.cs.meta rename to Assets/VRM10/Runtime/ControlRig/AnimatorPoseProvider.cs.meta diff --git a/Assets/VRM10/Runtime/ControlRig/ITPoseProvider.cs b/Assets/VRM10/Runtime/ControlRig/ITPoseProvider.cs index eb355237d..56c8c87b1 100644 --- a/Assets/VRM10/Runtime/ControlRig/ITPoseProvider.cs +++ b/Assets/VRM10/Runtime/ControlRig/ITPoseProvider.cs @@ -12,12 +12,20 @@ namespace UniVRM10 /// /// * world は ModelRoot を意図していることに注意 - /// * bone 無いときはどう振る舞うべきか + /// * bone 無いときは Quaternion.Identity ? /// /// /// Quaternion GetBoneTPoseWorldRotation(HumanBodyBones bone); + /// + /// * world は ModelRoot を意図していることに注意 + /// * bone 無いときは Vector3.zero ? + /// + /// + /// + Vector3 GetBoneTPoseWorldPosition(HumanBodyBones bone); + /// /// ボーンを親ボーンとセットで列挙する /// diff --git a/Assets/VRM10/Runtime/ControlRig/InitialRotationPoseProvider.cs b/Assets/VRM10/Runtime/ControlRig/InitialRotationPoseProvider.cs index 6b4c0b325..7ebbad0d8 100644 --- a/Assets/VRM10/Runtime/ControlRig/InitialRotationPoseProvider.cs +++ b/Assets/VRM10/Runtime/ControlRig/InitialRotationPoseProvider.cs @@ -65,5 +65,10 @@ namespace UniVRM10 { throw new System.NotImplementedException(); } + + public Vector3 GetBoneTPoseWorldPosition(HumanBodyBones bone) + { + throw new System.NotImplementedException(); + } } } diff --git a/Assets/VRM10/Runtime/ControlRig/NormalizedPoseProvider.cs b/Assets/VRM10/Runtime/ControlRig/NormalizedPoseProvider.cs deleted file mode 100644 index 7d2ba7874..000000000 --- a/Assets/VRM10/Runtime/ControlRig/NormalizedPoseProvider.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; - -namespace UniVRM10 -{ - /// - /// 正規化済みのヒエラルキーの getter。 - /// localRotation をコピーするだけなのだが、 - /// GetNormalizedLocalRotation にて、ボーンの有無(upperChestなど)の違いの対応をする予定(未実装)。 - /// - public sealed class NormalizedPoseProvider : INormalizedPoseProvider, ITPoseProvider - { - Transform m_root; - Animator m_animator; - - public Vector3 HipTPoseWorldPosition => throw new System.NotImplementedException(); - - public NormalizedPoseProvider(Transform root, Animator animator) - { - m_root = root; - m_animator = animator; - } - - public Quaternion GetNormalizedLocalRotation(HumanBodyBones bone, HumanBodyBones parentBone) - { - if (m_animator.GetBoneTransform(bone) is Transform t) - { - // TODO: parentBone relative - return t.localRotation; - } - else - { - // Debug.LogWarning($"{bone} not found"); - return Quaternion.identity; - } - } - - public Vector3 GetHipsPosition() - { - // TODO: from model root ? - return m_animator.GetBoneTransform(HumanBodyBones.Hips).localPosition; - } - - public Quaternion GetBoneTPoseWorldRotation(HumanBodyBones bone) - { - throw new System.NotImplementedException(); - } - - public IEnumerable<(HumanBodyBones Head, HumanBodyBones Parent)> EnumerateBones() - { - throw new System.NotImplementedException(); - } - } -} diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10Loaded.cs b/Assets/VRM10_Samples/VRM10Viewer/VRM10Loaded.cs index 9fbde3277..c2e2198dc 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/VRM10Loaded.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10Loaded.cs @@ -88,11 +88,5 @@ namespace UniVRM10.VRM10Viewer // destroy GameObject GameObject.Destroy(m_instance.gameObject); } - - public void TPoseControlRig() - { - var controlRig = m_controller.Runtime.ControlRig; - controlRig.EnforceTPose(); - } } } diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10Motion.cs b/Assets/VRM10_Samples/VRM10Viewer/VRM10Motion.cs index 08107b32b..d2fef7ba7 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/VRM10Motion.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10Motion.cs @@ -23,7 +23,7 @@ namespace UniVRM10.VRM10Viewer public VRM10Motion(UniHumanoid.BvhImporterContext context) { m_context = context; - var provider = new NormalizedPoseProvider(m_context.Root.transform, m_context.Root.GetComponent()); + var provider = new AnimatorPoseProvider(m_context.Root.transform, m_context.Root.GetComponent()); ControlRig = (provider, provider); } @@ -199,10 +199,5 @@ namespace UniVRM10.VRM10Viewer return motion; } } - - public void Retarget(Vrm10RuntimeControlRig dst) - { - VRM10Retarget.Retarget(ControlRig, (dst, dst)); - } } } diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10Retarget.cs b/Assets/VRM10_Samples/VRM10Viewer/VRM10Retarget.cs index 3af5fc621..8875c1bcb 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/VRM10Retarget.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10Retarget.cs @@ -1,3 +1,5 @@ +using UnityEngine; + namespace UniVRM10 { public static class VRM10Retarget @@ -9,7 +11,21 @@ namespace UniVRM10 var q = source.Pose.GetNormalizedLocalRotation(head, parent); sink.Pose.SetNormalizedLocalRotation(head, q); } - sink.Pose.SetHipsPosition(source.Pose.GetHipsPosition()); + + // scaling hips position + var scaling = sink.TPose.GetBoneTPoseWorldPosition(HumanBodyBones.LeftUpperLeg).y / source.TPose.GetBoneTPoseWorldPosition(HumanBodyBones.LeftUpperLeg).y; + var delta = source.Pose.GetHipsPosition() - source.TPose.HipTPoseWorldPosition; + sink.Pose.SetHipsPosition(sink.TPose.HipTPoseWorldPosition + delta * scaling); + } + + public static void EnforceTPose((INormalizedPoseApplicable Pose, ITPoseProvider TPose) sink) + { + foreach (var (head, parent) in sink.TPose.EnumerateBones()) + { + sink.Pose.SetNormalizedLocalRotation(head, Quaternion.identity); + } + + sink.Pose.SetHipsPosition(sink.TPose.HipTPoseWorldPosition); } } } diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs b/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs index 42424e86d..2c8c9bf3a 100644 --- a/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs +++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs @@ -237,11 +237,11 @@ namespace UniVRM10.VRM10Viewer { if (m_ui.IsBvhEnabled && m_src != null) { - m_src.Retarget(m_loaded.ControlRig); + VRM10Retarget.Retarget(m_src.ControlRig, (m_loaded.ControlRig, m_loaded.ControlRig)); } else { - m_loaded.TPoseControlRig(); + VRM10Retarget.EnforceTPose((m_loaded.ControlRig, m_loaded.ControlRig)); } } }