Merge pull request #1992 from ousttrue/feature/vrm10viewer_use_controlrig

[sample] VRM10Retarget の実装
This commit is contained in:
ousttrue
2023-02-27 17:39:05 +09:00
committed by GitHub
28 changed files with 513 additions and 438 deletions

View File

@@ -6,178 +6,6 @@ using System.Linq;
namespace UniHumanoid
{
public class BvhException : Exception
{
public BvhException(string msg) : base(msg) { }
}
public enum Channel
{
Xposition,
Yposition,
Zposition,
Xrotation,
Yrotation,
Zrotation,
}
public static class ChannelExtensions
{
public static string ToProperty(this Channel ch)
{
switch (ch)
{
case Channel.Xposition: return "localPosition.x";
case Channel.Yposition: return "localPosition.y";
case Channel.Zposition: return "localPosition.z";
case Channel.Xrotation: return "localEulerAnglesBaked.x";
case Channel.Yrotation: return "localEulerAnglesBaked.y";
case Channel.Zrotation: return "localEulerAnglesBaked.z";
}
throw new BvhException("no property for " + ch);
}
public static bool IsLocation(this Channel ch)
{
switch (ch)
{
case Channel.Xposition:
case Channel.Yposition:
case Channel.Zposition: return true;
case Channel.Xrotation:
case Channel.Yrotation:
case Channel.Zrotation: return false;
}
throw new BvhException("no property for " + ch);
}
}
public struct Single3
{
public Single x;
public Single y;
public Single z;
public Single3(Single _x, Single _y, Single _z)
{
x = _x;
y = _y;
z = _z;
}
}
public class BvhNode
{
public String Name
{
get;
private set;
}
public Single3 Offset
{
get;
private set;
}
public Channel[] Channels
{
get;
private set;
}
public List<BvhNode> Children
{
get;
private set;
}
public BvhNode(string name)
{
Name = name;
Children = new List<BvhNode>();
}
public virtual void Parse(StringReader r)
{
Offset = ParseOffset(r.ReadLine());
Channels = ParseChannel(r.ReadLine());
}
static Single3 ParseOffset(string line)
{
var split = line.Trim().Split();
if (split[0] != "OFFSET")
{
throw new BvhException("OFFSET is not found");
}
var offset = split.Skip(1).Where(x => !string.IsNullOrEmpty(x)).Select(x => float.Parse(x, System.Globalization.CultureInfo.InvariantCulture)).ToArray();
return new Single3(offset[0], offset[1], offset[2]);
}
static Channel[] ParseChannel(string line)
{
var split = line.Trim().Split();
if (split[0] != "CHANNELS")
{
throw new BvhException("CHANNELS is not found");
}
var count = int.Parse(split[1]);
if (count + 2 != split.Length)
{
throw new BvhException("channel count is not match with split count");
}
return split.Skip(2).Select(x => (Channel)Enum.Parse(typeof(Channel), x)).ToArray();
}
public IEnumerable<BvhNode> Traverse()
{
yield return this;
foreach (var child in Children)
{
foreach (var descendant in child.Traverse())
{
yield return descendant;
}
}
}
}
public class EndSite : BvhNode
{
public EndSite() : base("")
{
}
public override void Parse(StringReader r)
{
r.ReadLine(); // offset
}
}
public class ChannelCurve
{
public float[] Keys
{
get;
private set;
}
public ChannelCurve(int frameCount)
{
Keys = new float[frameCount];
}
public void SetKey(int frame, float value)
{
Keys[frame] = value;
}
}
public class Bvh
{
public BvhNode Root
@@ -192,7 +20,7 @@ namespace UniHumanoid
private set;
}
public ChannelCurve[] Channels
public BvhChannelCurve[] Channels
{
get;
private set;
@@ -211,7 +39,7 @@ namespace UniHumanoid
public bool IsLocation;
}
public bool TryGetPathWithPropertyFromChannel(ChannelCurve channel, out PathWithProperty pathWithProp)
public bool TryGetPathWithPropertyFromChannel(BvhChannelCurve channel, out PathWithProperty pathWithProp)
{
var index = Channels.ToList().IndexOf(channel);
if (index == -1)
@@ -270,7 +98,7 @@ namespace UniHumanoid
return null;
}
public ChannelCurve GetChannel(BvhNode target, Channel channel)
public BvhChannelCurve GetChannel(BvhNode target, BvhChannel channel)
{
var index = 0;
foreach (var node in Root.Traverse())
@@ -306,7 +134,7 @@ namespace UniHumanoid
.Select(x => x.Channels.Length)
.Sum();
Channels = Enumerable.Range(0, channelCount)
.Select(x => new ChannelCurve(frames))
.Select(x => new BvhChannelCurve(frames))
.ToArray()
;
}
@@ -410,7 +238,7 @@ namespace UniHumanoid
{
throw new BvhException("End in level 0");
}
node = new EndSite();
node = new BvhEndSite();
}
else
{
@@ -433,7 +261,7 @@ namespace UniHumanoid
break;
}
if (!(child is EndSite))
if (!(child is BvhEndSite))
{
node.Children.Add(child);
}

View File

@@ -0,0 +1,45 @@
namespace UniHumanoid
{
public enum BvhChannel
{
Xposition,
Yposition,
Zposition,
Xrotation,
Yrotation,
Zrotation,
}
public static class BvhChannelExtensions
{
public static string ToProperty(this BvhChannel ch)
{
switch (ch)
{
case BvhChannel.Xposition: return "localPosition.x";
case BvhChannel.Yposition: return "localPosition.y";
case BvhChannel.Zposition: return "localPosition.z";
case BvhChannel.Xrotation: return "localEulerAnglesBaked.x";
case BvhChannel.Yrotation: return "localEulerAnglesBaked.y";
case BvhChannel.Zrotation: return "localEulerAnglesBaked.z";
}
throw new BvhException("no property for " + ch);
}
public static bool IsLocation(this BvhChannel ch)
{
switch (ch)
{
case BvhChannel.Xposition:
case BvhChannel.Yposition:
case BvhChannel.Zposition: return true;
case BvhChannel.Xrotation:
case BvhChannel.Yrotation:
case BvhChannel.Zrotation: return false;
}
throw new BvhException("no property for " + ch);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 885c621c7dd0faf4e8e3de2e0187d2ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
namespace UniHumanoid
{
public class BvhChannelCurve
{
public float[] Keys
{
get;
private set;
}
public BvhChannelCurve(int frameCount)
{
Keys = new float[frameCount];
}
public void SetKey(int frame, float value)
{
Keys[frame] = value;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 588638a01ee0b7c47bd731aeed170cea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,16 @@
using System.IO;
namespace UniHumanoid
{
public class BvhEndSite : BvhNode
{
public BvhEndSite() : base("")
{
}
public override void Parse(StringReader r)
{
r.ReadLine(); // offset
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc2103bbe9c95224e9458e1fb058140f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,9 @@
using System;
namespace UniHumanoid
{
public class BvhException : Exception
{
public BvhException(string msg) : base(msg) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 86267a6edfc8fc749948c586e622846f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
namespace UniHumanoid
{
public class BvhNode
{
public String Name
{
get;
private set;
}
public Vector3 Offset
{
get;
private set;
}
public BvhChannel[] Channels
{
get;
private set;
}
public List<BvhNode> Children
{
get;
private set;
}
public BvhNode(string name)
{
Name = name;
Children = new List<BvhNode>();
}
public int GetChannelIndex(BvhChannel 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());
Channels = ParseChannel(r.ReadLine());
}
private static Vector3 ParseOffset(string line)
{
string[] split = line.Trim().Split();
if (split[0] != "OFFSET")
{
throw new BvhException("OFFSET is not found");
}
var offset = split.Skip(1).Where(x => !string.IsNullOrEmpty(x)).Select(x => float.Parse(x, System.Globalization.CultureInfo.InvariantCulture)).ToArray();
return new Vector3(offset[0], offset[1], offset[2]);
}
private static BvhChannel[] ParseChannel(string line)
{
var split = line.Trim().Split();
if (split[0] != "CHANNELS")
{
throw new BvhException("CHANNELS is not found");
}
var count = int.Parse(split[1]);
if (count + 2 != split.Length)
{
throw new BvhException("channel count is not match with split count");
}
return split.Skip(2).Select(x => (BvhChannel)Enum.Parse(typeof(BvhChannel), x)).ToArray();
}
public IEnumerable<BvhNode> Traverse()
{
yield return this;
foreach (var child in Children)
{
foreach (var descendant in child.Traverse())
{
yield return descendant;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f4ac3efd1faca94381a344a5be51cc2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -16,9 +16,9 @@ namespace UniHumanoid
Node = node;
}
public ChannelCurve PositionX;
public ChannelCurve PositionY;
public ChannelCurve PositionZ;
public BvhChannelCurve PositionX;
public BvhChannelCurve PositionY;
public BvhChannelCurve PositionZ;
public Vector3 GetPosition(int i)
{
return new Vector3(
@@ -27,9 +27,9 @@ namespace UniHumanoid
PositionZ.Keys[i]);
}
public ChannelCurve RotationX;
public ChannelCurve RotationY;
public ChannelCurve RotationZ;
public BvhChannelCurve RotationX;
public BvhChannelCurve RotationY;
public BvhChannelCurve RotationZ;
public Quaternion GetRotation(int i)
{
if (EulerToRotation == null)
@@ -43,7 +43,7 @@ namespace UniHumanoid
);
}
static void AddCurve(Bvh bvh, AnimationClip clip, ChannelCurve ch, float scaling)
static void AddCurve(Bvh bvh, AnimationClip clip, BvhChannelCurve ch, float scaling)
{
if (ch == null) return;
var pathWithProp = default(Bvh.PathWithProperty);
@@ -106,12 +106,12 @@ namespace UniHumanoid
var curve = bvh.Channels[j];
switch (node.Channels[i])
{
case Channel.Xposition: set.PositionX = curve; break;
case Channel.Yposition: set.PositionY = curve; break;
case Channel.Zposition: set.PositionZ = curve; break;
case Channel.Xrotation: set.RotationX = curve; break;
case Channel.Yrotation: set.RotationY = curve; break;
case Channel.Zrotation: set.RotationZ = curve; break;
case BvhChannel.Xposition: set.PositionX = curve; break;
case BvhChannel.Yposition: set.PositionY = curve; break;
case BvhChannel.Zposition: set.PositionZ = curve; break;
case BvhChannel.Xrotation: set.RotationX = curve; break;
case BvhChannel.Yrotation: set.RotationY = curve; break;
case BvhChannel.Zrotation: set.RotationZ = curve; break;
default: throw new Exception();
}
}

View File

@@ -75,11 +75,15 @@ namespace UniHumanoid
//
// scaling. reposition
//
var yCh = Bvh.Root.GetChannelIndex(BvhChannel.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())

View File

@@ -9,7 +9,7 @@ namespace UniHumanoid
{
public static Func<float, float, float, Quaternion> GetEulerToRotation(this BvhNode bvh)
{
var order = bvh.Channels.Where(x => x == Channel.Xrotation || x == Channel.Yrotation || x == Channel.Zrotation).ToArray();
var order = bvh.Channels.Where(x => x == BvhChannel.Xrotation || x == BvhChannel.Yrotation || x == BvhChannel.Zrotation).ToArray();
return (x, y, z) =>
{
@@ -22,9 +22,9 @@ namespace UniHumanoid
{
switch (ch)
{
case Channel.Xrotation: r = r * xRot; break;
case Channel.Yrotation: r = r * yRot; break;
case Channel.Zrotation: r = r * zRot; break;
case BvhChannel.Xrotation: r = r * xRot; break;
case BvhChannel.Yrotation: r = r * yRot; break;
case BvhChannel.Zrotation: r = r * zRot; break;
default: throw new BvhException("no rotation");
}
}
@@ -32,12 +32,12 @@ namespace UniHumanoid
};
}
public static Vector3 ToVector3(this Single3 s3)
public static Vector3 ToVector3(this Vector3 s3)
{
return new Vector3(s3.x, s3.y, s3.z);
}
public static Vector3 ToXReversedVector3(this Single3 s3)
public static Vector3 ToXReversedVector3(this Vector3 s3)
{
return new Vector3(-s3.x, s3.y, s3.z);
}

View File

@@ -33,10 +33,11 @@ namespace UniVRM10
private readonly Quaternion _initialTargetLocalRotation;
private readonly Quaternion _initialTargetGlobalRotation;
public Vector3 InitialTargetGlobalPosition { get; }
private readonly List<Vrm10ControlBone> _children = new List<Vrm10ControlBone>();
public IReadOnlyList<Vrm10ControlBone> 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);
}
/// <summary>
@@ -76,32 +78,32 @@ namespace UniVRM10
}
}
public static Vrm10ControlBone Build(UniHumanoid.Humanoid humanoid, out Dictionary<HumanBodyBones, Vrm10ControlBone> boneMap)
public static Vrm10ControlBone Build(UniHumanoid.Humanoid humanoid, out Dictionary<HumanBodyBones, Vrm10ControlBone> 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<HumanBodyBones, Vrm10ControlBone>();
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<HumanBodyBones, Vrm10ControlBone> boneMap)
private static void BuildRecursively(UniHumanoid.Humanoid humanoid, Transform current, Vrm10ControlBone parent, Dictionary<HumanBodyBones, Vrm10ControlBone> 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);
}
}
}

View File

@@ -23,8 +23,6 @@ namespace UniVRM10
public IReadOnlyDictionary<HumanBodyBones, Vrm10ControlBone> Bones => _bones;
public Animator ControlRigAnimator { get; }
public float InitialHipsHeight => HipTPoseWorldPosition.y;
/// <summary>
/// humanoid に対して ControlRig を生成します
/// </summary>
@@ -35,11 +33,9 @@ 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);
var transformBonePairs = _bones.Select(kv => (kv.Value.ControlBone, kv.Key));
_controlRigAvatar = HumanoidLoader.LoadHumanoidAvatar(vrmRoot, transformBonePairs);
_controlRigAvatar.name = "Runtime Control Rig";
@@ -72,19 +68,11 @@ namespace UniVRM10
}
}
public void EnforceTPose()
#region INormalizedPoseApplicable
public void SetRawHipsPosition(Vector3 position)
{
SetHipsPosition(_hipBone.ControlBone.position);
foreach (var bone in _bones.Values)
{
bone.ControlBone.localRotation = Quaternion.identity;
}
}
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 +82,7 @@ namespace UniVRM10
t.ControlBone.localRotation = normalizedLocalRotation;
}
}
#endregion
IEnumerable<(HumanBodyBones Head, HumanBodyBones Parent)> Traverse(Vrm10ControlBone bone, Vrm10ControlBone parent = null)
{
@@ -108,7 +97,8 @@ namespace UniVRM10
}
}
public IEnumerable<(HumanBodyBones Head, HumanBodyBones Parent)> EnumerateBones()
#region ITPoseProvider
public IEnumerable<(HumanBodyBones Bone, HumanBodyBones Parent)> EnumerateBoneParentPairs()
{
foreach (var headParent in Traverse(_hipBone))
{
@@ -116,11 +106,15 @@ namespace UniVRM10
}
}
public Vector3 HipTPoseWorldPosition { get; }
public Quaternion GetBoneTPoseWorldRotation(HumanBodyBones bone)
public Quaternion? GetBoneWorldRotation(HumanBodyBones bone)
{
return Quaternion.identity;
}
public Vector3? GetBoneWorldPosition(HumanBodyBones bone)
{
return _bones[bone].InitialTargetGlobalPosition;
}
#endregion
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UniVRM10
{
/// <summary>
/// Animator から Pose 供給する。
/// この Animator は以下の条件を満たすこと。
///
/// * TPoseであること
/// * 正規化済みであること
///
/// </summary>
public sealed class AnimatorPoseProvider : INormalizedPoseProvider, ITPoseProvider
{
Transform m_root;
Animator m_animator;
Dictionary<HumanBodyBones, Vector3> m_posMap = new Dictionary<HumanBodyBones, Vector3>();
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 GetRawHipsPosition()
{
// TODO: from model root ?
return m_animator.GetBoneTransform(HumanBodyBones.Hips).localPosition;
}
#endregion
#region ITPoseProvider
public Vector3 HipTPoseWorldPosition { get; }
public IEnumerable<(HumanBodyBones Bone, HumanBodyBones Parent)> EnumerateBoneParentPairs()
{
throw new System.NotImplementedException();
}
public Quaternion? GetBoneWorldRotation(HumanBodyBones bone)
{
throw new System.NotImplementedException();
}
public Vector3? GetBoneWorldPosition(HumanBodyBones bone)
{
return m_posMap[bone];
}
#endregion
}
}

View File

@@ -5,7 +5,7 @@ namespace UniVRM10
{
public interface INormalizedPoseApplicable
{
void SetHipsPosition(Vector3 position);
void SetRawHipsPosition(Vector3 position);
void SetNormalizedLocalRotation(HumanBodyBones bone, Quaternion normalizedLocalRotation);
}

View File

@@ -7,7 +7,7 @@ namespace UniVRM10
/// <summary>
/// Get hips position in model root space
/// </summary>
Vector3 GetHipsPosition();
Vector3 GetRawHipsPosition();
/// <summary>
/// Get normalized local rotation

View File

@@ -6,22 +6,15 @@ namespace UniVRM10
public interface ITPoseProvider
{
/// <summary>
/// * world は ModelRoot を意図していることに注意
/// </summary>
Vector3 HipTPoseWorldPosition { get; }
/// <summary>
/// * world は ModelRoot を意図していることに注意
/// * bone 無いときはどう振る舞うべきか
/// </summary>
/// <param name="bone"></param>
/// <returns></returns>
Quaternion GetBoneTPoseWorldRotation(HumanBodyBones bone);
/// <summary>
/// ボーンを親ボーンとセットで列挙する</returns>
/// このTPoseに含まれるボーンと親ボーンの組み合わせを列挙する。
///
/// * Humanoidの木構造を深さ優先の順番で列挙する
///
/// </summary>
/// <returns>hips の場合は parent は HumanBodyBones.LastBone で null を表す</returns>
IEnumerable<(HumanBodyBones Head, HumanBodyBones Parent)> EnumerateBones();
IEnumerable<(HumanBodyBones Bone, HumanBodyBones Parent)> EnumerateBoneParentPairs();
Quaternion? GetBoneWorldRotation(HumanBodyBones bone);
Vector3? GetBoneWorldPosition(HumanBodyBones bone);
}
}

View File

@@ -44,7 +44,7 @@ namespace UniVRM10
}
}
public Vector3 GetHipsPosition()
public Vector3 GetRawHipsPosition()
{
if (m_hips.parent == m_root)
{
@@ -56,12 +56,17 @@ namespace UniVRM10
}
}
public Quaternion GetBoneTPoseWorldRotation(HumanBodyBones bone)
public IEnumerable<(HumanBodyBones Bone, HumanBodyBones Parent)> EnumerateBoneParentPairs()
{
throw new System.NotImplementedException();
}
public IEnumerable<(HumanBodyBones Head, HumanBodyBones Parent)> EnumerateBones()
public Quaternion? GetBoneWorldRotation(HumanBodyBones bone)
{
throw new System.NotImplementedException();
}
public Vector3? GetBoneWorldPosition(HumanBodyBones bone)
{
throw new System.NotImplementedException();
}

View File

@@ -1,54 +0,0 @@
using System.Collections.Generic;
using UnityEngine;
namespace UniVRM10
{
/// <summary>
/// 正規化済みのヒエラルキーの getter。
/// localRotation をコピーするだけなのだが、
/// GetNormalizedLocalRotation にて、ボーンの有無(upperChestなど)の違いの対応をする予定(未実装)。
/// </summary>
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();
}
}
}

View File

@@ -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;
@@ -87,115 +88,5 @@ namespace UniVRM10.VRM10Viewer
// destroy GameObject
GameObject.Destroy(m_instance.gameObject);
}
/// <summary>
/// from v0.103
/// </summary>
/// <param name="src"></param>
public void UpdateControlRigExplicit(Animator src)
{
var controlRig = m_controller.Runtime.ControlRig;
foreach (HumanBodyBones bone in CachedEnum.GetValues<HumanBodyBones>())
{
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;
}
}
}
/// <summary>
/// from v0.104
/// </summary>
public void UpdateControlRigImplicit(Animator src)
{
var dst = m_controller.GetComponent<Animator>();
foreach (HumanBodyBones bone in CachedEnum.GetValues<HumanBodyBones>())
{
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;
}
}
}
/// <summary>
/// from v0.108
/// </summary>
public void UpdateControlRigImplicit(UniHumanoid.Humanoid src)
{
var dst = m_controller.GetComponent<Animator>();
foreach (HumanBodyBones bone in CachedEnum.GetValues<HumanBodyBones>())
{
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;
controlRig.EnforceTPose();
}
}
}

View File

@@ -0,0 +1,72 @@
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 AnimatorPoseProvider(m_context.Root.transform, m_context.Root.GetComponent<Animator>());
ControlRig = (provider, provider);
}
public VRM10Motion(UniGLTF.RuntimeGltfInstance instance)
{
m_instance = instance;
if (instance.GetComponent<Animation>() is Animation animation)
{
animation.Play();
}
}
public void ShowBoxMan(bool showBoxMan)
{
if (m_context != null)
{
m_context.Root.GetComponent<SkinnedMeshRenderer>().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<Transform> Traverse(Transform t)
{
yield return t;
foreach (Transform child in t)
{
foreach (var x in Traverse(child))
{
yield return x;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08f06b12436bf594b9982c6a8ec71374
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,15 +1,31 @@
namespace UniVRM10
using UnityEngine;
namespace UniVRM10.VRM10Viewer
{
public static class VRM10Retarget
{
public static void Retarget((INormalizedPoseProvider Pose, ITPoseProvider TPose) source, (INormalizedPoseApplicable Pose, ITPoseProvider TPose) sink)
{
foreach (var (head, parent) in sink.TPose.EnumerateBones())
foreach (var (head, parent) in sink.TPose.EnumerateBoneParentPairs())
{
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.GetBoneWorldPosition(HumanBodyBones.LeftUpperLeg).Value.y / source.TPose.GetBoneWorldPosition(HumanBodyBones.LeftUpperLeg).Value.y;
var delta = source.Pose.GetRawHipsPosition() - source.TPose.GetBoneWorldPosition(HumanBodyBones.Hips).Value;
sink.Pose.SetRawHipsPosition(sink.TPose.GetBoneWorldPosition(HumanBodyBones.Hips).Value + delta * scaling);
}
public static void EnforceTPose((INormalizedPoseApplicable Pose, ITPoseProvider TPose) sink)
{
foreach (var (bone, parent) in sink.TPose.EnumerateBoneParentPairs())
{
sink.Pose.SetNormalizedLocalRotation(bone, Quaternion.identity);
}
sink.Pose.SetRawHipsPosition(sink.TPose.GetBoneWorldPosition(HumanBodyBones.Hips).Value);
}
}
}

View File

@@ -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<Text>();
m_version = texts.First(x => x.name == "Version");
m_src = GameObject.FindObjectOfType<Animator>();
m_target = GameObject.FindObjectOfType<VRM10TargetMover>().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<Animator>();
m_ui.IsBvhEnabled = true;
// hide box man
context.Root.GetComponent<SkinnedMeshRenderer>().enabled = false;
}
private void Update()
{
if (m_loaded != null)
@@ -254,11 +237,11 @@ namespace UniVRM10.VRM10Viewer
{
if (m_ui.IsBvhEnabled && m_src != null)
{
m_loaded.UpdateControlRigImplicit(m_src);
VRM10Retarget.Retarget(m_src.ControlRig, (m_loaded.ControlRig, m_loaded.ControlRig));
}
else
{
m_loaded.TPoseControlRig();
VRM10Retarget.EnforceTPose((m_loaded.ControlRig, m_loaded.ControlRig));
}
}
}
@@ -280,7 +263,7 @@ namespace UniVRM10.VRM10Viewer
var ext = Path.GetExtension(path).ToLower();
if (ext == ".bvh")
{
LoadMotion(path);
m_src = VRM10Motion.LoadBvhFromPath(path);
return;
}