Skip to content

Commit

Permalink
fix: Fix misalignment in expression preview and thumbnail rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
suzuryg committed Dec 15, 2023
1 parent 6426e65 commit 251ff8a
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ public class AV3Constants
public static readonly string Path_GeneratedDir = $"Assets/Suzuryg/{DomainConstants.SystemName}/Generated";
public static readonly string Path_PrefabDir = $"Assets/Suzuryg/{DomainConstants.SystemName}/Prefabs";

public static readonly string GUID_TPoseClip = "645a7092829eff9478fb3a29f959a6fa";

public static readonly IReadOnlyList<HandGesture> EmoteSelectToGesture = new List<HandGesture>()
{
HandGesture.Neutral,
Expand Down
101 changes: 94 additions & 7 deletions Packages/jp.suzuryg.face-emo/Editor/Detail/AV3/AV3Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -346,15 +346,102 @@ public static Animator GetAnimator(AV3Setting aV3Setting)
return aV3Setting.TargetAvatar.GetComponent<Animator>();
}

public static AnimationClip GetAvatarPoseClip()
private static HumanPose _poseCache;
private static AnimationClip _poseClipCache;
public static AnimationClip GetAvatarPoseClip(VRCAvatarDescriptor avatarDescriptor)
{
// TODO: Support for avatar posture other than T-pose
var tPose = AssetDatabase.LoadAssetAtPath<AnimationClip>(AssetDatabase.GUIDToAssetPath(AV3Constants.GUID_TPoseClip));
if (tPose == null)
if (avatarDescriptor == null) { return null; }
var clonedAvatar = UnityEngine.Object.Instantiate(avatarDescriptor.gameObject);
try
{
clonedAvatar.transform.localPosition = Vector3.zero;
clonedAvatar.transform.localRotation = Quaternion.identity;
// FIXME: Unable to support the case that avatar's body shape balance is tuned by root object's scale. (Is it necessary to assume this case...?)
clonedAvatar.transform.localScale = Vector3.one;

var animator = clonedAvatar.GetComponent<Animator>();
if (animator == null || !animator.isHuman) { return null; }

var humanPoseHandler = new HumanPoseHandler(animator.avatar, animator.transform);
var humanPose = new HumanPose();
humanPoseHandler.GetHumanPose(ref humanPose);

if (_poseClipCache == null ||
!ArePosesSimilar(humanPose, _poseCache, float.Epsilon, float.Epsilon, float.Epsilon))
{
_poseCache = humanPose;
_poseClipCache = new AnimationClip() { legacy = false };
SetDefaultTransformToClip(_poseClipCache);
SetHumanPoseToClip(ref humanPose, _poseClipCache);
}

return _poseClipCache;
}
finally
{
UnityEngine.Object.DestroyImmediate(clonedAvatar);
}
}

private static bool ArePosesSimilar(HumanPose pose1, HumanPose pose2, float positionThreshold, float rotationThreshold, float muscleThreshold)
{
// Compare body position
if (Vector3.Distance(pose1.bodyPosition, pose2.bodyPosition) > positionThreshold)
{
return false;
}

// Compare body rotation
if (Quaternion.Angle(pose1.bodyRotation, pose2.bodyRotation) > rotationThreshold)
{
return false;
}

// Compare muscle values
for (int i = 0; i < pose1.muscles.Length; i++)
{
if (Mathf.Abs(pose1.muscles[i] - pose2.muscles[i]) > muscleThreshold)
{
return false;
}
}

// If none of the values are beyond the thresholds, the poses are considered similar
return true;
}

private static void SetDefaultTransformToClip(AnimationClip clip)
{
// FIXME: Scale change is reflected correctly in AnimationWindow preview, but not in AnimationMode sampling, so avatar scale must be changed directly.
// This scale change is useless because it is not reflected, but leave it for now.
clip.SetCurve("", typeof(Transform), "m_LocalScale.x", new AnimationCurve(new Keyframe(time: 0, value: 1)));
clip.SetCurve("", typeof(Transform), "m_LocalScale.y", new AnimationCurve(new Keyframe(time: 0, value: 1)));
clip.SetCurve("", typeof(Transform), "m_LocalScale.z", new AnimationCurve(new Keyframe(time: 0, value: 1)));

clip.SetCurve("", typeof(Animator), "MotionT.x", new AnimationCurve(new Keyframe(time: 0, value: 0)));
clip.SetCurve("", typeof(Animator), "MotionT.y", new AnimationCurve(new Keyframe(time: 0, value: 0)));
clip.SetCurve("", typeof(Animator), "MotionT.z", new AnimationCurve(new Keyframe(time: 0, value: 0)));
}

private static void SetHumanPoseToClip(ref HumanPose humanPose, AnimationClip clip)
{
// Set body position and rotation
clip.SetCurve("", typeof(Animator), "RootT.x", new AnimationCurve(new Keyframe(0, humanPose.bodyPosition.x)));
clip.SetCurve("", typeof(Animator), "RootT.y", new AnimationCurve(new Keyframe(0, humanPose.bodyPosition.y)));
clip.SetCurve("", typeof(Animator), "RootT.z", new AnimationCurve(new Keyframe(0, humanPose.bodyPosition.z)));

clip.SetCurve("", typeof(Animator), "RootQ.x", new AnimationCurve(new Keyframe(0, humanPose.bodyRotation.x)));
clip.SetCurve("", typeof(Animator), "RootQ.y", new AnimationCurve(new Keyframe(0, humanPose.bodyRotation.y)));
clip.SetCurve("", typeof(Animator), "RootQ.z", new AnimationCurve(new Keyframe(0, humanPose.bodyRotation.z)));
clip.SetCurve("", typeof(Animator), "RootQ.w", new AnimationCurve(new Keyframe(0, humanPose.bodyRotation.w)));

// Set muscle values
for (int i = 0; i < humanPose.muscles.Length; i++)
{
Debug.LogError("T-pose animation clip not found.");
string muscleName = HumanTrait.MuscleName[i];
float muscleValue = humanPose.muscles[i];
clip.SetCurve("", typeof(Animator), muscleName, new AnimationCurve(new Keyframe(0, muscleValue)));
}
return tPose;
}

public static AnimationClip SynthesizeClip(AnimationClip baseClip, AnimationClip additionalClip)
Expand All @@ -375,7 +462,7 @@ public static AnimationClip SynthesizeClip(AnimationClip baseClip, AnimationClip
return synthesized;
}

public static AnimationClip SynthesizeAvatarPose(AnimationClip animationClip) => SynthesizeClip(GetAvatarPoseClip(), animationClip);
public static AnimationClip SynthesizeAvatarPose(AnimationClip animationClip, VRCAvatarDescriptor avatarDescriptor) => SynthesizeClip(GetAvatarPoseClip(avatarDescriptor), animationClip);

public static void CombineExpressions(AnimationClip leftHand, AnimationClip rightHand, AnimationClip destination)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ public void StartSampling()

if (_previewAvatar != null) { UnityEngine.Object.DestroyImmediate(_previewAvatar); }
_previewAvatar = UnityEngine.Object.Instantiate(avatarRoot);
// FIXME: Unable to support the case that avatar's body shape balance is tuned by root object's scale. (Is it necessary to assume this case...?)
_previewAvatar.transform.localScale = Vector3.one;
_previewAvatar.SetActive(true);
_previewAvatar.hideFlags = HideFlags.HideAndDontSave;

Expand Down Expand Up @@ -176,7 +178,7 @@ public void StopSampling()
public Vector3 GetAvatarViewPosition()
{
// Returns view position if previewable in T-pose.
if (AV3Utility.GetAvatarPoseClip() != null && (_aV3Setting?.TargetAvatar as VRCAvatarDescriptor)?.ViewPosition != null)
if (AV3Utility.GetAvatarPoseClip(_aV3Setting?.TargetAvatar as VRCAvatarDescriptor) != null && (_aV3Setting?.TargetAvatar as VRCAvatarDescriptor)?.ViewPosition != null)
{
return (_aV3Setting.TargetAvatar as VRCAvatarDescriptor).ViewPosition + new Vector3(PreviewAvatarPosX, PreviewAvatarPosY, PreviewAvatarPosZ);
}
Expand All @@ -194,12 +196,15 @@ public Vector3 GetAvatarHeadPosition()
if (avatarRoot != null)
{
var clonedAvatar = UnityEngine.Object.Instantiate(avatarRoot);
// FIXME: Unable to support the case that avatar's body shape balance is tuned by root object's scale. (Is it necessary to assume this case...?)
clonedAvatar.transform.localScale = Vector3.one;

try
{
var animator = clonedAvatar.GetComponent<Animator>();
if (animator != null && animator.isHuman)
{
var clip = AV3Utility.GetAvatarPoseClip();
var clip = AV3Utility.GetAvatarPoseClip(_aV3Setting?.TargetAvatar as VRCAvatarDescriptor);
if (clip == null) { clip = new AnimationClip(); }
AnimationMode.StartAnimationMode();
AnimationMode.BeginSampling();
Expand Down Expand Up @@ -566,7 +571,7 @@ private void RenderPreviewClip()

private void InitializePreviewClip()
{
_previewClip = AV3Utility.SynthesizeAvatarPose(Clip);
_previewClip = AV3Utility.SynthesizeAvatarPose(Clip, _aV3Setting?.TargetAvatar as VRCAvatarDescriptor);
RenderPreviewClip();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ private IEnumerator UpdateCoroutine()
{
// Clone avatar
clonedAvatar = UnityEngine.Object.Instantiate(avatarAnimator.gameObject);
// FIXME: Unable to support the case that avatar's body shape balance is tuned by root object's scale. (Is it necessary to assume this case...?)
clonedAvatar.transform.localScale = Vector3.one;
SceneManager.MoveGameObjectToScene(clonedAvatar, _previewScene);
clonedAvatar.transform.position = Vector3.zero;
clonedAvatar.transform.rotation = Quaternion.identity;
Expand Down Expand Up @@ -345,7 +347,7 @@ private Texture2D RenderAnimatedAvatar(string clipGUID, GameObject animatorRoot,
}

// Synthesize avatar pose
var synthesized = AV3Utility.SynthesizeAvatarPose(clip);
var synthesized = AV3Utility.SynthesizeAvatarPose(clip, _aV3Setting?.TargetAvatar as VRCAvatarDescriptor);

// Sample animation clip and render
var positionCache = animatorRoot.transform.position;
Expand Down

0 comments on commit 251ff8a

Please sign in to comment.