From 64f5a05bc3844ab559d8be12384f98f8214adf6d Mon Sep 17 00:00:00 2001 From: ousttrue Date: Tue, 23 Aug 2022 15:06:19 +0900 Subject: [PATCH] Vrm10FkRetarget --- .../UniGLTF/Runtime/UniHumanoid/Humanoid.cs | 14 +++ .../Vrm10Runtime/Vrm10BoneWithAxis.cs | 108 ++++++++++++++++++ .../Vrm10Runtime/Vrm10BoneWithAxis.cs.meta | 11 ++ .../Vrm10Runtime/Vrm10FkRetarget.cs | 38 ++++++ .../Vrm10Runtime/Vrm10FkRetarget.cs.meta | 11 ++ .../Components/Vrm10Runtime/Vrm10Runtime.cs | 16 +++ 6 files changed, 198 insertions(+) create mode 100644 Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs create mode 100644 Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs.meta create mode 100644 Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs create mode 100644 Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs.meta diff --git a/Assets/UniGLTF/Runtime/UniHumanoid/Humanoid.cs b/Assets/UniGLTF/Runtime/UniHumanoid/Humanoid.cs index 3e70358be..2b099488e 100644 --- a/Assets/UniGLTF/Runtime/UniHumanoid/Humanoid.cs +++ b/Assets/UniGLTF/Runtime/UniHumanoid/Humanoid.cs @@ -437,5 +437,19 @@ namespace UniHumanoid return true; } + + public bool TryGetBoneForTransform(Transform t, out HumanBodyBones bone) + { + foreach (var (v, k) in BoneMap) + { + if (v == t) + { + bone = k; + return true; + } + } + bone = default; + return false; + } } } diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs new file mode 100644 index 000000000..e131d2d1a --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + /// + /// このクラスのヒエラルキーが 正規化された TPose を表している。 + /// 同時に、元のヒエラルキーの初期回転を保持する。 + /// Apply 関数で、再帰的に正規化済みのローカル回転から初期回転を加味したローカル回転を作って適用する。 + /// + public class Vrm10BoneWithAxis + { + public readonly HumanBodyBones Bone; + + /// + /// 元のヒエラルキーの対応ボーン + /// + public readonly Transform Target; + + /// + /// 回転と拡大縮小を除去した(正規化された)ボーン。 + /// このボーンに対して localRotation を代入する。 + /// + public readonly Transform Normalized; + + /// + /// 元のボーンの初期回転。 + /// + public readonly Quaternion InitialLocalRotation; + + public readonly Quaternion ToLocal; + + public List Children = new List(); + + public Vrm10BoneWithAxis(Transform current, Quaternion parentInverse, HumanBodyBones bone) + { + if (bone == HumanBodyBones.LastBone) + { + throw new ArgumentNullException(); + } + if (current == null) + { + throw new ArgumentNullException(); + } + Bone = bone; + Target = current; + Normalized = new GameObject(bone.ToString()).transform; + Normalized.position = current.position; + // InitialLocalRotation = parentInverse * current.rotation; + InitialLocalRotation = current.localRotation; + // InitialLocalRotation = current.rotation; + ToLocal = current.rotation; + } + + public static Vrm10BoneWithAxis Build(UniHumanoid.Humanoid humanoid, Dictionary boneMap) + { + var hips = new Vrm10BoneWithAxis(humanoid.Hips, Quaternion.identity, HumanBodyBones.Hips); + + foreach (Transform child in humanoid.Hips) + { + Traverse(humanoid, child, hips, boneMap); + } + + return hips; + } + + private static void Traverse(UniHumanoid.Humanoid humanoid, Transform current, Vrm10BoneWithAxis parent, Dictionary boneMap) + { + if (humanoid.TryGetBoneForTransform(current, out var bone)) + { + + // ヒューマンボーンだけを対象にするので、 + // parent が current の直接の親でない場合がある。 + // ワールド回転 parent^-1 * current からローカル回転を算出する。 + var parentInverse = Quaternion.Inverse(parent.Target.rotation); + + var newBone = new Vrm10BoneWithAxis(current, parentInverse, bone); + newBone.Normalized.SetParent(parent.Normalized, true); + parent.Children.Add(newBone); + parent = newBone; + boneMap.Add(bone, newBone); + } + + foreach (Transform child in current) + { + Traverse(humanoid, child, parent, boneMap); + } + } + + /// + /// 親から再帰的にNormalized の ローカル回転を初期回転を加味して Target に適用する。 + /// + public void ApplyRecursive(Quaternion worldParentRotation) + { + // var pose = InitialLocalRotation * Normalized.localRotation * Quaternion.Inverse(InitialLocalRotation); + // var pose = Quaternion.Inverse(InitialLocalRotation) * Normalized.localRotation * InitialLocalRotation; + Target.localRotation = InitialLocalRotation * Quaternion.Inverse(ToLocal) * Normalized.localRotation * ToLocal; + // Target.localRotation = InitialLocalRotation * Normalized.localRotation; // * Quaternion.Inverse(InitialLocalRotation); + // Target.localRotation = InitialLocalRotation; + + foreach (var child in Children) + { + child.ApplyRecursive(Normalized.rotation); + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs.meta b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs.meta new file mode 100644 index 000000000..35e73e07c --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eac40a89a1de8bc4a8d97b3cc2aea5fb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs new file mode 100644 index 000000000..1b08b21b5 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace UniVRM10 +{ + public class Vrm10FkRetarget + { + Vrm10BoneWithAxis m_root; + + Dictionary m_boneMap = new Dictionary(); + + Vector3 m_initialHipsPosition; + + public Vrm10FkRetarget(UniHumanoid.Humanoid humanoid) + { + m_root = Vrm10BoneWithAxis.Build(humanoid, m_boneMap); + m_initialHipsPosition = m_root.Target.position; + } + + public void Apply() + { + m_root.Target.position = m_root.Normalized.position; + m_root.ApplyRecursive(Quaternion.identity); + } + + public Transform GetBoneTransform(HumanBodyBones bone) + { + if (m_boneMap.TryGetValue(bone, out var value)) + { + return value.Normalized; + } + else + { + return null; + } + } + } +} diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs.meta b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs.meta new file mode 100644 index 000000000..e48351021 --- /dev/null +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c0af836ff386dd469cfedbd238ab2a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs index 15f504d54..ddace679e 100644 --- a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs +++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10Runtime.cs @@ -14,6 +14,17 @@ namespace UniVRM10 public class Vrm10Runtime : IDisposable { private readonly Vrm10Instance m_target; + + Vrm10FkRetarget m_fkRetarget; + public Vrm10FkRetarget GetOrCreateFkRetarget() + { + if (m_fkRetarget == null) + { + m_fkRetarget = new Vrm10FkRetarget(m_target.Humanoid); + } + return m_fkRetarget; + } + private readonly IVrm10Constraint[] m_constraints; private readonly Transform m_head; private readonly FastSpringBoneService m_fastSpringBoneService; @@ -152,6 +163,11 @@ namespace UniVRM10 /// public void Process() { + if (m_fkRetarget != null) + { + m_fkRetarget.Apply(); + } + // // constraint //