using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using System.Reflection; namespace RootMotion { public class TQ { public TQ() { } public TQ(Vector3 translation, Quaternion rotation) { t = translation; q = rotation; } public Vector3 t; public Quaternion q; } /* Written with the kind help of the one commonly known as Mecanim-Dev. */ public class AvatarUtility { public static Quaternion GetPostRotation(Avatar avatar, AvatarIKGoal avatarIKGoal) { int humanId = (int)HumanIDFromAvatarIKGoal(avatarIKGoal); if (humanId == (int)HumanBodyBones.LastBone) throw new InvalidOperationException("Invalid human id."); MethodInfo methodGetPostRotation = typeof(Avatar).GetMethod("GetPostRotation", BindingFlags.Instance | BindingFlags.NonPublic); if (methodGetPostRotation == null) throw new InvalidOperationException("Cannot find GetPostRotation method."); return (Quaternion)methodGetPostRotation.Invoke(avatar, new object[] { humanId }); } /// /// Get IK position and rotation for foot/hand bone position/rotation. /// public static TQ GetIKGoalTQ(Avatar avatar, float humanScale, AvatarIKGoal avatarIKGoal, TQ bodyPositionRotation, TQ boneTQ) { int humanId = (int)HumanIDFromAvatarIKGoal(avatarIKGoal); if (humanId == (int)HumanBodyBones.LastBone) throw new InvalidOperationException("Invalid human id."); MethodInfo methodGetAxisLength = typeof(Avatar).GetMethod("GetAxisLength", BindingFlags.Instance | BindingFlags.NonPublic); if (methodGetAxisLength == null) throw new InvalidOperationException("Cannot find GetAxisLength method."); MethodInfo methodGetPostRotation = typeof(Avatar).GetMethod("GetPostRotation", BindingFlags.Instance | BindingFlags.NonPublic); if (methodGetPostRotation == null) throw new InvalidOperationException("Cannot find GetPostRotation method."); Quaternion postRotation = (Quaternion)methodGetPostRotation.Invoke(avatar, new object[] { humanId }); var goalTQ = new TQ(boneTQ.t, boneTQ.q * postRotation); if (avatarIKGoal == AvatarIKGoal.LeftFoot || avatarIKGoal == AvatarIKGoal.RightFoot) { // Here you could use animator.leftFeetBottomHeight or animator.rightFeetBottomHeight rather than GetAxisLenght // Both are equivalent but GetAxisLength is the generic way and work for all human bone float axislength = (float)methodGetAxisLength.Invoke(avatar, new object[] { humanId }); Vector3 footBottom = new Vector3(axislength, 0, 0); goalTQ.t += (goalTQ.q * footBottom); } // IK goal are in avatar body local space Quaternion invRootQ = Quaternion.Inverse(bodyPositionRotation.q); goalTQ.t = invRootQ * (goalTQ.t - bodyPositionRotation.t); goalTQ.q = invRootQ * goalTQ.q; goalTQ.t /= humanScale; goalTQ.q = Quaternion.LookRotation(goalTQ.q * Vector3.forward, goalTQ.q * Vector3.up); return goalTQ; } public static TQ WorldSpaceIKGoalToBone(TQ goalTQ, Avatar avatar, AvatarIKGoal avatarIKGoal) { int humanId = (int)HumanIDFromAvatarIKGoal(avatarIKGoal); if (humanId == (int)HumanBodyBones.LastBone) throw new InvalidOperationException("Invalid human id."); MethodInfo methodGetAxisLength = typeof(Avatar).GetMethod("GetAxisLength", BindingFlags.Instance | BindingFlags.NonPublic); if (methodGetAxisLength == null) throw new InvalidOperationException("Cannot find GetAxisLength method."); MethodInfo methodGetPostRotation = typeof(Avatar).GetMethod("GetPostRotation", BindingFlags.Instance | BindingFlags.NonPublic); if (methodGetPostRotation == null) throw new InvalidOperationException("Cannot find GetPostRotation method."); Quaternion postRotation = (Quaternion)methodGetPostRotation.Invoke(avatar, new object[] { humanId }); if (avatarIKGoal == AvatarIKGoal.LeftFoot || avatarIKGoal == AvatarIKGoal.RightFoot) { // Here you could use animator.leftFeetBottomHeight or animator.rightFeetBottomHeight rather than GetAxisLenght // Both are equivalent but GetAxisLength is the generic way and work for all human bone float axislength = (float)methodGetAxisLength.Invoke(avatar, new object[] { humanId }); Vector3 footBottom = new Vector3(axislength, 0, 0); goalTQ.t -= (goalTQ.q * footBottom); } TQ boneTQ = new TQ(goalTQ.t, goalTQ.q * Quaternion.Inverse(postRotation)); return boneTQ; } public static TQ GetWorldSpaceIKGoal(BakerHumanoidQT ikQT, BakerHumanoidQT rootQT, float time, float humanScale) { var tq = ikQT.Evaluate(time); var rTQ = rootQT.Evaluate(time); tq.q = rTQ.q * tq.q; tq.t = rTQ.t + rTQ.q * tq.t; tq.t *= humanScale; return tq; } public static HumanBodyBones HumanIDFromAvatarIKGoal(AvatarIKGoal avatarIKGoal) { switch (avatarIKGoal) { case AvatarIKGoal.LeftFoot: return HumanBodyBones.LeftFoot; case AvatarIKGoal.RightFoot: return HumanBodyBones.RightFoot; case AvatarIKGoal.LeftHand: return HumanBodyBones.LeftHand; case AvatarIKGoal.RightHand: return HumanBodyBones.RightHand; default: return HumanBodyBones.LastBone; } } } }