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..da931757a
--- /dev/null
+++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10BoneWithAxis.cs
@@ -0,0 +1,104 @@
+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);
+ boneMap.Add(HumanBodyBones.Hips, 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)
+ {
+ Target.localRotation = InitialLocalRotation * Quaternion.Inverse(ToLocal) * Normalized.localRotation * ToLocal;
+ 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..f91d4bcda
--- /dev/null
+++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10FkRetarget.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace UniVRM10
+{
+ public class Vrm10FkRetarget
+ {
+ Vrm10BoneWithAxis m_root;
+
+ Dictionary m_boneMap = new Dictionary();
+
+ public readonly float InitialHipsHeight;
+
+ public Vrm10FkRetarget(UniHumanoid.Humanoid humanoid)
+ {
+ m_root = Vrm10BoneWithAxis.Build(humanoid, m_boneMap);
+ InitialHipsHeight = m_root.Target.position.y;
+ Debug.Log($"InitialHipsHeight: {InitialHipsHeight}");
+ }
+
+ 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
//
diff --git a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeLookAt.cs b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeLookAt.cs
index 7aa88d374..36ba102f8 100644
--- a/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeLookAt.cs
+++ b/Assets/VRM10/Runtime/Components/Vrm10Runtime/Vrm10RuntimeLookAt.cs
@@ -107,16 +107,15 @@ namespace UniVRM10
gaze.SetParent(m_head);
gaze.localPosition = Vector3.forward;
}
- switch (m_lookat.LookAtType)
+
+ // bone が無いときのエラー防止。マイグレーション失敗?
+ if (m_lookat.LookAtType == LookAtType.bone && m_leftEye != null && m_rightEye != null)
{
- case LookAtType.bone:
- _eyeDirectionApplicable = new LookAtEyeDirectionApplicableToBone(m_leftEye, m_rightEye, m_lookat.HorizontalOuter, m_lookat.HorizontalInner, m_lookat.VerticalDown, m_lookat.VerticalUp);
- break;
- case LookAtType.expression:
- _eyeDirectionApplicable = new LookAtEyeDirectionApplicableToExpression(m_lookat.HorizontalOuter, m_lookat.HorizontalInner, m_lookat.VerticalDown, m_lookat.VerticalUp);
- break;
- default:
- throw new ArgumentOutOfRangeException();
+ _eyeDirectionApplicable = new LookAtEyeDirectionApplicableToBone(m_leftEye, m_rightEye, m_lookat.HorizontalOuter, m_lookat.HorizontalInner, m_lookat.VerticalDown, m_lookat.VerticalUp);
+ }
+ else
+ {
+ _eyeDirectionApplicable = new LookAtEyeDirectionApplicableToExpression(m_lookat.HorizontalOuter, m_lookat.HorizontalInner, m_lookat.VerticalDown, m_lookat.VerticalUp);
}
}
diff --git a/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs b/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs
index 7b56b4676..00ca86a68 100644
--- a/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs
+++ b/Assets/VRM10_Samples/VRM10Viewer/VRM10ViewerUI.cs
@@ -39,7 +39,7 @@ namespace UniVRM10.VRM10Viewer
[Header("Runtime")]
[SerializeField]
- HumanPoseTransfer m_src = default;
+ Animator m_src = default;
[SerializeField]
GameObject m_target = default;
@@ -200,7 +200,7 @@ namespace UniVRM10.VRM10Viewer
var texts = GameObject.FindObjectsOfType();
m_version = texts.First(x => x.name == "Version");
- m_src = GameObject.FindObjectOfType();
+ m_src = GameObject.FindObjectOfType();
m_target = GameObject.FindObjectOfType().gameObject;
}
@@ -208,7 +208,7 @@ namespace UniVRM10.VRM10Viewer
class Loaded : IDisposable
{
RuntimeGltfInstance m_instance;
- HumanPoseTransfer m_pose;
+ bool m_useBvh;
Vrm10Instance m_controller;
VRM10AIUEO m_lipSync;
@@ -256,7 +256,7 @@ namespace UniVRM10.VRM10Viewer
}
}
- public Loaded(RuntimeGltfInstance instance, HumanPoseTransfer src, Transform lookAtTarget)
+ public Loaded(RuntimeGltfInstance instance, Transform lookAtTarget)
{
m_instance = instance;
@@ -266,10 +266,6 @@ namespace UniVRM10.VRM10Viewer
// VRM
m_controller.UpdateType = Vrm10Instance.UpdateTypes.LateUpdate; // after HumanPoseTransfer's setPose
{
- m_pose = instance.gameObject.AddComponent();
- m_pose.Source = src;
- m_pose.SourceType = HumanPoseTransfer.HumanPoseTransferSourceType.HumanPoseTransfer;
-
m_lipSync = instance.gameObject.AddComponent();
m_blink = instance.gameObject.AddComponent();
m_autoExpression = instance.gameObject.AddComponent();
@@ -293,22 +289,57 @@ namespace UniVRM10.VRM10Viewer
GameObject.Destroy(m_instance.gameObject);
}
- public void EnableBvh(HumanPoseTransfer src)
+ public void EnableBvh()
{
- if (m_pose != null)
- {
- m_pose.Source = src;
- m_pose.SourceType = HumanPoseTransfer.HumanPoseTransferSourceType.HumanPoseTransfer;
- }
+ m_useBvh = true;
}
- public void EnableTPose(HumanPoseClip pose)
+ public void EnableTPose()
{
- if (m_pose != null)
+ m_useBvh = false;
+ }
+
+ public void UpdatePose(Animator pose)
+ {
+ var fkRetarget = m_controller.Runtime.GetOrCreateFkRetarget();
+ foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
{
- m_pose.PoseClip = pose;
- m_pose.SourceType = HumanPoseTransfer.HumanPoseTransferSourceType.HumanPoseClip;
+ if (bone == HumanBodyBones.LastBone)
+ {
+ continue;
+ }
+
+ var dst = fkRetarget.GetBoneTransform(bone);
+ if (dst == null)
+ {
+ continue;
+ }
+
+ if (m_useBvh)
+ {
+ var src = pose.GetBoneTransform(bone);
+ if (src != null)
+ {
+ // set normalized pose
+ dst.localRotation = src.localRotation;
+ }
+
+ if (bone == HumanBodyBones.Hips)
+ {
+ dst.position = src.position * fkRetarget.InitialHipsHeight;
+ }
+ }
+ else
+ {
+ // set TPose
+ dst.localRotation = Quaternion.identity;
+ if (bone == HumanBodyBones.Hips)
+ {
+ dst.position = Vector3.up * fkRetarget.InitialHipsHeight;
+ }
+ }
}
+ fkRetarget.Apply();
}
}
Loaded m_loaded;
@@ -344,7 +375,7 @@ namespace UniVRM10.VRM10Viewer
var context = new UniHumanoid.BvhImporterContext();
context.Parse("tmp.bvh", source);
context.Load();
- SetMotion(context.Root.GetComponent());
+ SetMotion(context.Root.GetComponent());
}
private void Update()
@@ -369,7 +400,12 @@ namespace UniVRM10.VRM10Viewer
}
}
- m_ui.UpdateToggle(() => m_loaded?.EnableBvh(m_src), () => m_loaded?.EnableTPose(m_pose));
+ m_ui.UpdateToggle(() => m_loaded?.EnableBvh(), () => m_loaded?.EnableTPose());
+
+ if (m_loaded != null)
+ {
+ m_loaded.UpdatePose(m_src);
+ }
}
void OnOpenClicked()
@@ -506,17 +542,15 @@ namespace UniVRM10.VRM10Viewer
instance.ShowMeshes();
instance.EnableUpdateWhenOffscreen();
- m_loaded = new Loaded(instance, m_src, m_target.transform);
+ m_loaded = new Loaded(instance, m_target.transform);
}
- void SetMotion(HumanPoseTransfer src)
+ void SetMotion(Animator src)
{
m_src = src;
- src.GetComponent().enabled = false;
-
if (m_loaded != null)
{
- m_loaded.EnableBvh(m_src);
+ m_loaded.EnableBvh();
}
}
}