initial upload

This commit is contained in:
tom.hempel
2025-09-21 22:42:26 +02:00
commit d03bcd4ba5
6231 changed files with 351582 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 490fde02f41ae014abec4edfce28648f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,23 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RootMotion
{
public abstract class AnimationModifier : MonoBehaviour
{
protected Animator animator;
protected Baker baker;
public virtual void OnInitiate(Baker baker, Animator animator)
{
this.baker = baker;
this.animator = animator;
}
public virtual void OnStartClip(AnimationClip clip) { }
public virtual void OnBakerUpdate(float normalizedTime) { }
}
}

View File

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

View File

@ -0,0 +1,60 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RootMotion
{
public class AnimationModifierStack : MonoBehaviour
{
public AnimationModifier[] modifiers = new AnimationModifier[0];
private Animator animator;
private Baker baker;
private void Start()
{
animator = GetComponent<Animator>();
baker = GetComponent<Baker>();
baker.OnStartClip += OnBakerStartClip;
baker.OnUpdateClip += OnBakerUpdateClip;
foreach (AnimationModifier m in modifiers)
{
m.OnInitiate(baker, animator);
}
}
private void OnBakerStartClip(AnimationClip clip, float normalizedTime)
{
foreach (AnimationModifier m in modifiers)
{
m.OnStartClip(clip);
}
}
private void OnBakerUpdateClip(AnimationClip clip, float normalizedTime)
{
foreach (AnimationModifier m in modifiers)
{
if (!m.enabled) continue;
m.OnBakerUpdate(normalizedTime);
}
}
private void LateUpdate()
{
if (!animator.enabled && !baker.isBaking) return;
if (baker.isBaking && baker.mode == Baker.Mode.AnimationClips) return;
if (animator.runtimeAnimatorController == null) return;
AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0);
float n = info.normalizedTime;
foreach (AnimationModifier m in modifiers)
{
if (!m.enabled) continue;
m.OnBakerUpdate(n);
}
}
}
}

View File

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

View File

@ -0,0 +1,602 @@
using UnityEngine;
using System.Collections;
using UnityEngine.Playables;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace RootMotion
{
/// <summary>
/// Base class for animation bakers, handles timing, keyframing and saving AnimationClips.
/// </summary>
[HelpURL("http://www.root-motion.com/finalikdox/html/page3.html")]
[AddComponentMenu("Scripts/RootMotion/Baker")]
public abstract class Baker : MonoBehaviour
{
// Open the User Manual URL
[ContextMenu("User Manual")]
void OpenUserManual()
{
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page3.html");
}
// Open the Script Reference URL
[ContextMenu("Scrpt Reference")]
void OpenScriptReference()
{
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_baker.html");
}
// Link to the Final IK Google Group
[ContextMenu("Support Group")]
void SupportGroup()
{
Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik");
}
// Link to the Final IK Asset Store thread in the Unity Community
[ContextMenu("Asset Store Thread")]
void ASThread()
{
Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/");
}
[System.Serializable]
public enum Mode
{
AnimationClips = 0,
AnimationStates = 1,
PlayableDirector = 2,
Realtime = 3
}
/// <summary>
/// In AnimationClips, AnimationStates or PlayableDirector mode - the frame rate at which the animation clip will be sampled. In Realtime mode - the frame rate at which the pose will be sampled. With the latter, the frame rate is not guaranteed if the player is not able to reach it.
/// </summary>
[Tooltip("In AnimationClips, AnimationStates or PlayableDirector mode - the frame rate at which the animation clip will be sampled. In Realtime mode - the frame rate at which the pose will be sampled. With the latter, the frame rate is not guaranteed if the player is not able to reach it.")]
[Range(1, 90)] public int frameRate = 30;
/// <summary>
/// Maximum allowed error for keyframe reduction.
/// </summary>
[Tooltip("Maximum allowed error for keyframe reduction.")]
[Range(0f, 0.1f)] public float keyReductionError = 0.01f;
/// <summary>
/// AnimationClips mode can be used to bake a batch of AnimationClips directly without the need of setting up an AnimatorController. AnimationStates mode is useful for when you need to set up a more complex rig with layers and AvatarMasks in Mecanim. PlayableDirector mode bakes a Timeline. Realtime mode is for continuous baking of gameplay, ragdoll phsysics or PuppetMaster dynamics.
/// </summary>
[Tooltip("AnimationClips mode can be used to bake a batch of AnimationClips directly without the need of setting up an AnimatorController. " +
"AnimationStates mode is useful for when you need to set up a more complex rig with layers and AvatarMasks in Mecanim. " +
"PlayableDirector mode bakes a Timeline. " +
"Realtime mode is for continuous baking of gameplay, ragdoll phsysics or PuppetMaster dynamics.")]
public Mode mode;
/// <summary>
/// AnimationClips to bake.
/// </summary>
[Tooltip("AnimationClips to bake.")]
public AnimationClip[] animationClips = new AnimationClip[0];
/// <summary>
/// The name of the AnimationStates to bake (must be on the base layer) in the Animator (Right-click on this component header and select 'Find Animation States' to have Baker fill those in automatically, required that state names match with the names of the clips used in them).
/// </summary>
[Tooltip("The name of the AnimationStates to bake (must be on the base layer) in the Animator above (Right-click on this component header and select 'Find Animation States' to have Baker fill those in automatically, required that state names match with the names of the clips used in them).")]
public string[] animationStates = new string[0];
/// <summary>
/// The folder to save the baked AnimationClips to.
/// </summary>
[Tooltip("The folder to save the baked AnimationClips to.")]
public string saveToFolder = "Assets";
/// <summary>
/// String that will be added to each clip or animation state name for the saved clip. For example if your animation state/clip names were 'Idle' and 'Walk', then with '_Baked' as Append Name, the Baker will create 'Idle_Baked' and 'Walk_Baked' animation clips.
/// </summary>
[Tooltip("String that will be added to each clip or animation state name for the saved clip. For example if your animation state/clip names were 'Idle' and 'Walk', then with '_Baked' as Append Name, the Baker will create 'Idle_Baked' and 'Walk_Baked' animation clips.")]
public string appendName = "_Baked";
/// <summary>
/// Name of the created AnimationClip file.
/// </summary>
[Tooltip("Name of the created AnimationClip file.")]
public string saveName = "Baked Clip";
public bool isBaking { get; private set; }
public float bakingProgress { get; private set; }
[HideInInspector] public Animator animator;
[HideInInspector] public PlayableDirector director;
public delegate void BakerDelegate(AnimationClip clip, float time);
public BakerDelegate OnStartClip;
public BakerDelegate OnUpdateClip;
[System.Serializable]
public class ClipSettings
{
[System.Serializable]
public enum BasedUponRotation
{
Original = 0,
BodyOrientation = 1,
}
[System.Serializable]
public enum BasedUponY
{
Original = 0,
CenterOfMass = 1,
Feet = 2,
}
[System.Serializable]
public enum BasedUponXZ
{
Original = 0,
CenterOfMass = 1,
}
public bool loopTime;
public bool loopBlend;
public float cycleOffset;
public bool loopBlendOrientation;
public BasedUponRotation basedUponRotation;
public float orientationOffsetY;
public bool loopBlendPositionY;
public BasedUponY basedUponY;
public float level;
public bool loopBlendPositionXZ;
public BasedUponXZ basedUponXZ;
public bool mirror;
#if UNITY_EDITOR
public void ApplyTo(AnimationClipSettings settings)
{
settings.loopTime = loopTime;
settings.loopBlend = loopBlend;
settings.cycleOffset = cycleOffset;
settings.loopBlendOrientation = loopBlendOrientation;
settings.keepOriginalOrientation = basedUponRotation == BasedUponRotation.Original;
settings.orientationOffsetY = orientationOffsetY;
settings.loopBlendPositionY = loopBlendPositionY;
settings.keepOriginalPositionY = basedUponY == BasedUponY.Original;
settings.heightFromFeet = basedUponY == BasedUponY.Feet;
settings.level = level;
settings.loopBlendPositionXZ = loopBlendPositionXZ;
settings.keepOriginalPositionXZ = basedUponXZ == BasedUponXZ.Original;
settings.mirror = mirror;
}
#endif
}
/// <summary>
/// If enabled, baked clips will have the same AnimationClipSettings as the clips used for baking. If disabled, clip settings from below will be applied to all the baked clips.
/// </summary>
[Tooltip("If enabled, baked clips will have the same AnimationClipSettings as the clips used for baking. If disabled, clip settings from below will be applied to all the baked clips.")] public bool inheritClipSettings;
/// <summary>
/// AnimationClipSettings applied to the baked animation clip.
/// </summary>
[Tooltip("AnimationClipSettings applied to the baked animation clip.")] public ClipSettings clipSettings;
protected abstract Transform GetCharacterRoot();
protected abstract void OnStartBaking();
protected abstract void OnSetLoopFrame(float time);
protected abstract void OnSetCurves(ref AnimationClip clip);
protected abstract void OnSetKeyframes(float time, bool lastFrame);
protected float clipLength { get; private set; }
protected bool addLoopFrame;
#if UNITY_EDITOR
private AnimationClip[] bakedClips = new AnimationClip[0];
private AnimatorStateInfo currentClipStateInfo;
private int currentClipIndex;
private float startBakingTime;
private float nextKeyframeTime;
private bool firstFrame;
private int clipFrames;
private int clipFrameNo;
private bool setKeyframes;
private float currentClipTime;
private float clipFrameInterval;
#endif
// Start baking an animation state, clip or timeline, also called for each next clip in the baking array
public void BakeClip()
{
#if UNITY_EDITOR
if (mode == Mode.AnimationClips && inheritClipSettings)
{
AnimationClipSettings originalSettings = AnimationUtility.GetAnimationClipSettings(animationClips[currentClipIndex]);
addLoopFrame = originalSettings.loopTime;
}
else
{
if (mode == Mode.Realtime) addLoopFrame = clipSettings.loopTime && clipSettings.loopBlend;
else addLoopFrame = clipSettings.loopTime;
}
StartBaking();
#endif
}
public void StartBaking()
{
#if UNITY_EDITOR
isBaking = true;
nextKeyframeTime = 0f;
bakingProgress = 0f;
clipFrameNo = 0;
if (bakedClips.Length == 0)
{
switch (mode)
{
case Mode.AnimationClips:
bakedClips = new AnimationClip[animationClips.Length];
break;
case Mode.AnimationStates:
bakedClips = new AnimationClip[animationStates.Length];
break;
default:
bakedClips = new AnimationClip[1];
break;
}
}
OnStartBaking();
firstFrame = true;
#endif
}
public void StopBaking()
{
#if UNITY_EDITOR
if (!isBaking) return;
if (addLoopFrame)
{
OnSetLoopFrame(clipLength);
}
bakedClips[currentClipIndex] = new AnimationClip();
bakedClips[currentClipIndex].frameRate = frameRate;
OnSetCurves(ref bakedClips[currentClipIndex]);
bakedClips[currentClipIndex].EnsureQuaternionContinuity();
if (mode == Mode.Realtime)
{
isBaking = false;
SaveClips();
}
#endif
}
#if UNITY_EDITOR
[ContextMenu("Find Animation States")]
public void FindAnimationStates()
{
animator = GetComponent<Animator>();
if (animator == null)
{
Debug.LogError("No Animator found on Baker GameObject. Can not find animation states.");
return;
}
if (animator.runtimeAnimatorController == null)
{
Debug.LogError("Animator does not have a valid Controller. Can not find animation states.");
return;
}
var clips = animator.runtimeAnimatorController.animationClips;
animationStates = new string[clips.Length];
for (int i = 0; i < clips.Length; i++)
{
animationStates[i] = clips[i].name;
}
}
void Update()
{
// Baking procedure
if (isBaking)
{
if (mode != Mode.Realtime)
{
if (firstFrame)
{
transform.position = Vector3.zero;
transform.rotation = Quaternion.identity;
GetCharacterRoot().position = Vector3.zero;
GetCharacterRoot().rotation = Quaternion.identity;
StartAnimationUpdate();
currentClipTime = 0f;
firstFrame = false;
}
switch (mode)
{
case Mode.AnimationClips:
clipLength = animationClips[currentClipIndex].length;
bakingProgress = currentClipTime / clipLength;
break;
case Mode.AnimationStates:
currentClipStateInfo = animator.GetCurrentAnimatorStateInfo(0);
bakingProgress = currentClipStateInfo.normalizedTime;
if (currentClipStateInfo.speed <= 0f)
{
Debug.LogError("Baker can not bake a clip with 0 speed.");
return;
}
clipLength = currentClipStateInfo.length / currentClipStateInfo.speed / animator.speed;
break;
case Mode.PlayableDirector:
clipLength = (float)director.duration;
bakingProgress = (float)director.time / clipLength;
break;
}
clipFrames = (int)(clipLength * (frameRate));
clipFrameInterval = clipLength / (float)(clipFrames);
setKeyframes = true;
// Stop clip baking if the clip is finished, start baking the next clip if possible
if (clipFrameNo > clipFrames)
{
StopBaking();
currentClipIndex++;
if (currentClipIndex >= bakedClips.Length)
{
currentClipIndex = 0;
StopAnimationUpdate();
isBaking = false;
SaveClips();
return;
}
else
{
BakeClip();
setKeyframes = false;
}
}
if (!firstFrame) AnimationUpdate();
}
}
}
void LateUpdate()
{
if (!isBaking) return;
if (mode != Mode.Realtime)
{
if (setKeyframes)
{
OnSetKeyframes(clipFrameNo * clipFrameInterval, clipFrameNo >= clipFrames);
clipFrameNo++;
setKeyframes = false;
}
}
else
{
if (firstFrame)
{
startBakingTime = Time.time;
firstFrame = false;
}
if (Time.time < nextKeyframeTime) return;
OnSetKeyframes(Time.time - startBakingTime, false);
nextKeyframeTime = Time.time + (1f / (float)frameRate);
}
}
private void StartAnimationUpdate()
{
switch (mode)
{
case Mode.AnimationClips:
if (!AnimationMode.InAnimationMode()) AnimationMode.StartAnimationMode();
AnimationMode.BeginSampling();
AnimationMode.SampleAnimationClip(gameObject, animationClips[currentClipIndex], 0f);
AnimationMode.EndSampling();
if (OnStartClip != null) OnStartClip(animationClips[currentClipIndex], 0f);
break;
case Mode.AnimationStates:
animator.enabled = false;
animator.Play(animationStates[currentClipIndex], 0, 0f);
break;
case Mode.PlayableDirector:
director.enabled = false;
director.time = 0f;
director.Evaluate();
break;
}
}
private void StopAnimationUpdate()
{
switch (mode)
{
case Mode.AnimationClips:
if (AnimationMode.InAnimationMode()) AnimationMode.StopAnimationMode();
break;
case Mode.AnimationStates:
animator.enabled = true;
break;
case Mode.PlayableDirector:
director.enabled = true;
break;
}
}
private void AnimationUpdate()
{
switch (mode)
{
case Mode.AnimationClips:
if (!AnimationMode.InAnimationMode()) AnimationMode.StartAnimationMode();
AnimationMode.BeginSampling();
AnimationMode.SampleAnimationClip(gameObject, animationClips[currentClipIndex], currentClipTime);
AnimationMode.EndSampling();
if (OnUpdateClip != null) OnUpdateClip(animationClips[currentClipIndex], currentClipTime / animationClips[currentClipIndex].length);
currentClipTime += clipFrameInterval;
break;
case Mode.AnimationStates:
animator.Update(clipFrameInterval);
break;
case Mode.PlayableDirector:
director.time = currentClipTime;
director.Evaluate();
currentClipTime += clipFrameInterval;
break;
}
}
public void SaveClips()
{
var clips = GetBakedClips();
AnimationClip savedClip = null;
for (int i = 0; i < clips.Length; i++)
{
string path = GetFullPath(i);
if (mode == Mode.AnimationClips && inheritClipSettings)
{
AnimationClipSettings inheritedSettings = AnimationUtility.GetAnimationClipSettings(animationClips[i]);
SetClipSettings(clips[i], inheritedSettings);
AnimationUtility.SetAnimationClipSettings(clips[i], inheritedSettings);
}
else
{
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(clips[i]);
clipSettings.ApplyTo(settings);
SetClipSettings(clips[i], settings);
AnimationUtility.SetAnimationClipSettings(clips[i], settings);
}
var existing = AssetDatabase.LoadAssetAtPath(path, typeof(AnimationClip)) as AnimationClip;
if (existing != null)
{
// Overwrite
EditorUtility.CopySerialized(clips[i], existing);
}
else
{
// Create new asset
AssetDatabase.CreateAsset(clips[i], path);
}
/* v2.0
switch (mode)
{
case Baker.Mode.Realtime: break;
case Baker.Mode.AnimationClips:
AnimationClipSettings originalSettings = AnimationUtility.GetAnimationClipSettings(animationClips[i]);
SetClipSettings(clips[i], originalSettings);
AnimationUtility.SetAnimationClipSettings(clips[i], originalSettings);
break;
default:
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(clips[i]);
settings.loopTime = loop;
SetClipSettings(clips[i], settings);
AnimationUtility.SetAnimationClipSettings(clips[i], settings);
break;
}
var existing = AssetDatabase.LoadAssetAtPath(path, typeof(AnimationClip)) as AnimationClip;
if (existing != null)
{
// Overwrite with existing settings
AnimationClipSettings existingSettings = AnimationUtility.GetAnimationClipSettings(existing);
existingSettings.stopTime = clips[i].length;
AnimationUtility.SetAnimationClipSettings(clips[i], existingSettings);
EditorUtility.CopySerialized(clips[i], existing);
}
else
{
// Create new asset
AssetDatabase.CreateAsset(clips[i], path);
}
*/
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
savedClip = AssetDatabase.LoadAssetAtPath(path, typeof(AnimationClip)) as AnimationClip;
Debug.Log(path + " successfully baked.");
}
Selection.activeObject = savedClip;
ClearBakedClips();
}
protected virtual void SetClipSettings(AnimationClip clip, AnimationClipSettings settings) { }
private string GetFullPath(int clipIndex)
{
switch (mode)
{
case Baker.Mode.AnimationClips:
return saveToFolder + "/" + animationClips[clipIndex].name + appendName + ".anim";
case Baker.Mode.AnimationStates:
return saveToFolder + "/" + animationStates[clipIndex] + appendName + ".anim";
case Baker.Mode.PlayableDirector:
return saveToFolder + "/" + saveName + ".anim";
default:
return saveToFolder + "/" + saveName + ".anim";
}
}
private AnimationClip[] GetBakedClips()
{
return bakedClips;
}
private void ClearBakedClips()
{
bakedClips = new AnimationClip[0];
}
#endif
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: ab14efce3fefe4f4982f0fa6911d0cd4
timeCreated: 1516617114
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,134 @@
using UnityEngine;
using System.Collections;
using System;
namespace RootMotion
{
/// <summary>
/// Baker for Generic/Legacy animation.
/// </summary>
public class GenericBaker : Baker
{
/// <summary>
/// If true, produced AnimationClips will be marked as Legacy and usable with the Legacy animation system.
/// </summary>
[Tooltip("If true, produced AnimationClips will be marked as Legacy and usable with the Legacy animation system.")]
public bool markAsLegacy;
/// <summary>
/// Root Transform of the hierarchy to bake.
/// </summary>
[Tooltip("Root Transform of the hierarchy to bake.")]
public Transform root;
/// <summary>
/// Root Node used for root motion.
/// </summary>
[Tooltip("Root Node used for root motion.")]
public Transform rootNode;
/// <summary>
/// List of Transforms to ignore, rotation curves will not be baked for these Transforms.
/// </summary>
[Tooltip("List of Transforms to ignore, rotation curves will not be baked for these Transforms.")]
public Transform[] ignoreList;
/// <summary>
/// LocalPosition curves will be baked for these Transforms only. If you are baking a character, the pelvis bone should be added to this array.
/// </summary>
[Tooltip("LocalPosition curves will be baked for these Transforms only. If you are baking a character, the pelvis bone should be added to this array.")]
public Transform[] bakePositionList;
private BakerTransform[] children = new BakerTransform[0];
private BakerTransform rootChild;
private int rootChildIndex = -1;
void Awake()
{
// Find all the child Transforms of the Animator
Transform[] childrenAndRoot = (Transform[])root.GetComponentsInChildren<Transform>();
children = new BakerTransform[0];
// Exlude the ignore list, construct the children array
for (int i = 0; i < childrenAndRoot.Length; i++)
{
if (!IsIgnored(childrenAndRoot[i]))
{
Array.Resize(ref children, children.Length + 1);
bool isRootNode = childrenAndRoot[i] == rootNode;
if (isRootNode) rootChildIndex = children.Length - 1;
children[children.Length - 1] = new BakerTransform(childrenAndRoot[i], root, BakePosition(childrenAndRoot[i]), isRootNode);
}
}
}
protected override Transform GetCharacterRoot()
{
return root;
}
protected override void OnStartBaking()
{
for (int i = 0; i < children.Length; i++)
{
children[i].Reset();
if (i == rootChildIndex) children[i].SetRelativeSpace(root.position, root.rotation);
}
}
protected override void OnSetLoopFrame(float time)
{
// TODO Change to SetLoopFrame like in HumanoidBaker
for (int i = 0; i < children.Length; i++) children[i].AddLoopFrame(time);
}
protected override void OnSetCurves(ref AnimationClip clip)
{
// TODO Length Multiplier
for (int i = 0; i < children.Length; i++) children[i].SetCurves(ref clip, keyReductionError);
}
protected override void OnSetKeyframes(float time, bool lastFrame)
{
for (int i = 0; i < children.Length; i++) children[i].SetKeyframes(time);
}
// Is the specified Transform in the ignore list?
private bool IsIgnored(Transform t)
{
for (int i = 0; i < ignoreList.Length; i++)
{
if (t == ignoreList[i]) return true;
}
return false;
}
// Should we record the localPosition channels of the Transform?
private bool BakePosition(Transform t)
{
for (int i = 0; i < bakePositionList.Length; i++)
{
if (t == bakePositionList[i]) return true;
}
return false;
}
#if UNITY_EDITOR
protected override void SetClipSettings(AnimationClip clip, UnityEditor.AnimationClipSettings settings)
{
clip.legacy = markAsLegacy;
if (mode != Baker.Mode.AnimationClips)
{
clip.wrapMode = settings.loopTime ? WrapMode.Loop : WrapMode.Default;
}
}
#endif
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 27b32efee52144c4e934e6c3272ceb2c
timeCreated: 1516356533
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 15001
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: e468ef7e80a87f94588b5eeb3a423b91
folderAsset: yes
timeCreated: 1516621025
licenseType: Store
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,128 @@
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 });
}
/// <summary>
/// Get IK position and rotation for foot/hand bone position/rotation.
/// </summary>
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;
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2f9e403f0f8255c42b90254abc55f528
timeCreated: 1516435305
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,651 @@
using UnityEngine;
using System.Collections;
namespace RootMotion
{
public static class BakerUtilities
{
public static void ReduceKeyframes(AnimationCurve curve, float maxError)
{
if (maxError <= 0f) return;
curve.keys = GetReducedKeyframes(curve, maxError);
// TODO Flatten outTangent for keys that have the next key and testAfter sampled to the same value in the original clip. Same thing for the inTangent
}
public static Keyframe[] GetReducedKeyframes(AnimationCurve curve, float maxError)
{
Keyframe[] keys = curve.keys;
int i = 1;
while (i < keys.Length - 1 && keys.Length > 2)
{
Keyframe[] testKeys = new Keyframe[keys.Length - 1];
int c = 0;
for (int n = 0; n < keys.Length; n++)
{
if (i != n)
{
testKeys[c] = new Keyframe(keys[n].time, keys[n].value, keys[n].inTangent, keys[n].outTangent);
c++;
}
}
AnimationCurve testCurve = new AnimationCurve();
testCurve.keys = testKeys;
float test0 = Mathf.Abs(testCurve.Evaluate(keys[i].time) - keys[i].value);
float beforeTime = keys[i].time + (keys[i - 1].time - keys[i].time) * 0.5f;
float afterTime = keys[i].time + (keys[i + 1].time - keys[i].time) * 0.5f;
float testBefore = Mathf.Abs(testCurve.Evaluate(beforeTime) - curve.Evaluate(beforeTime));
float testAfter = Mathf.Abs(testCurve.Evaluate(afterTime) - curve.Evaluate(afterTime));
if (test0 < maxError && testBefore < maxError && testAfter < maxError)
{
keys = testKeys;
}
else
{
i++;
}
}
return keys;
}
public static void SetLoopFrame(float time, AnimationCurve curve)
{
/*
Keyframe[] keys = curve.keys;
keys[keys.Length - 1].value = keys[0].value;
keys[keys.Length - 1].inTangent = keys[0].inTangent;
keys[keys.Length - 1].outTangent = keys[0].outTangent;
keys[keys.Length - 1].time = time;
curve.keys = keys;
*/
Keyframe[] keys = curve.keys;
keys[keys.Length - 1].value = keys[0].value;
float inTangent = Mathf.Lerp(keys[0].inTangent, keys[keys.Length - 1].inTangent, 0.5f);
keys[0].inTangent = inTangent;
keys[keys.Length - 1].inTangent = inTangent;
float outTangent = Mathf.Lerp(keys[0].outTangent, keys[keys.Length - 1].outTangent, 0.5f);
keys[0].outTangent = outTangent;
keys[keys.Length - 1].outTangent = outTangent;
keys[keys.Length - 1].time = time;
curve.keys = keys;
}
public static void SetTangentMode(AnimationCurve curve)
{
#if UNITY_EDITOR
if (curve.length < 2) return;
for (int i = 1; i < curve.length - 1; i++)
{
UnityEditor.AnimationUtility.SetKeyLeftTangentMode(curve, i, UnityEditor.AnimationUtility.TangentMode.ClampedAuto);
UnityEditor.AnimationUtility.SetKeyRightTangentMode(curve, i, UnityEditor.AnimationUtility.TangentMode.ClampedAuto);
}
#endif
}
// Realigns quaternion keys to ensure shortest interpolation paths.
public static Quaternion EnsureQuaternionContinuity(Quaternion lastQ, Quaternion q)
{
Quaternion flipped = new Quaternion(-q.x, -q.y, -q.z, -q.w);
Quaternion midQ = new Quaternion(
Mathf.Lerp(lastQ.x, q.x, 0.5f),
Mathf.Lerp(lastQ.y, q.y, 0.5f),
Mathf.Lerp(lastQ.z, q.z, 0.5f),
Mathf.Lerp(lastQ.w, q.w, 0.5f)
);
Quaternion midQFlipped = new Quaternion(
Mathf.Lerp(lastQ.x, flipped.x, 0.5f),
Mathf.Lerp(lastQ.y, flipped.y, 0.5f),
Mathf.Lerp(lastQ.z, flipped.z, 0.5f),
Mathf.Lerp(lastQ.w, flipped.w, 0.5f)
);
float angle = Quaternion.Angle(lastQ, midQ);
float angleFlipped = Quaternion.Angle(lastQ, midQFlipped);
return angleFlipped < angle ? flipped : q;
}
}
//Manages the Animation Curves for Humanoid Q/T channels.
[System.Serializable]
public class BakerHumanoidQT
{
private Transform transform;
private string Qx, Qy, Qz, Qw;
private string Tx, Ty, Tz;
// Animation curves for each channel of the Transform
public AnimationCurve rotX, rotY, rotZ, rotW;
public AnimationCurve posX, posY, posZ;
private AvatarIKGoal goal;
private Quaternion lastQ;
private bool lastQSet;
// The custom constructor
public BakerHumanoidQT(string name)
{
Qx = name + "Q.x";
Qy = name + "Q.y";
Qz = name + "Q.z";
Qw = name + "Q.w";
Tx = name + "T.x";
Ty = name + "T.y";
Tz = name + "T.z";
Reset();
}
public BakerHumanoidQT(Transform transform, AvatarIKGoal goal, string name)
{
this.transform = transform;
this.goal = goal;
Qx = name + "Q.x";
Qy = name + "Q.y";
Qz = name + "Q.z";
Qw = name + "Q.w";
Tx = name + "T.x";
Ty = name + "T.y";
Tz = name + "T.z";
Reset();
}
public Quaternion EvaluateRotation(float time)
{
Quaternion q = new Quaternion(rotX.Evaluate(time), rotY.Evaluate(time), rotZ.Evaluate(time), rotW.Evaluate(time));
return q;
//return q.normalized;
}
public Vector3 EvaluatePosition(float time)
{
return new Vector3(posX.Evaluate(time), posY.Evaluate(time), posZ.Evaluate(time));
}
public TQ Evaluate(float time)
{
return new TQ(EvaluatePosition(time), EvaluateRotation(time));
}
public void GetCurvesFromClip(AnimationClip clip, Animator animator)
{
#if UNITY_EDITOR
rotX = GetEditorCurve(clip, Qx);
rotY = GetEditorCurve(clip, Qy);
rotZ = GetEditorCurve(clip, Qz);
rotW = GetEditorCurve(clip, Qw);
posX = GetEditorCurve(clip, Tx);
posY = GetEditorCurve(clip, Ty);
posZ = GetEditorCurve(clip, Tz);
#endif
}
#if UNITY_EDITOR
private AnimationCurve GetEditorCurve(AnimationClip clip, string propertyPath)
{
var binding = UnityEditor.EditorCurveBinding.FloatCurve(string.Empty, typeof(Animator), propertyPath);
return UnityEditor.AnimationUtility.GetEditorCurve(clip, binding);
}
#endif
// Clear all curves
public void Reset()
{
rotX = new AnimationCurve();
rotY = new AnimationCurve();
rotZ = new AnimationCurve();
rotW = new AnimationCurve();
posX = new AnimationCurve();
posY = new AnimationCurve();
posZ = new AnimationCurve();
lastQ = Quaternion.identity;
lastQSet = false;
}
public void SetIKKeyframes(float time, Avatar avatar, Transform root, float humanScale, Vector3 bodyPosition, Quaternion bodyRotation)
{
Vector3 bonePos = transform.position;
Quaternion boneRot = transform.rotation;
if (root.parent != null)
{
bonePos = root.parent.InverseTransformPoint(bonePos);
boneRot = Quaternion.Inverse(root.parent.rotation) * boneRot;
}
TQ IKTQ = AvatarUtility.GetIKGoalTQ(avatar, humanScale, goal, new TQ(bodyPosition, bodyRotation), new TQ(bonePos, boneRot));
Quaternion rot = IKTQ.q;
if (lastQSet) rot = BakerUtilities.EnsureQuaternionContinuity(lastQ, IKTQ.q);
//rot.Normalize();
lastQ = rot;
lastQSet = true;
rotX.AddKey(time, rot.x);
rotY.AddKey(time, rot.y);
rotZ.AddKey(time, rot.z);
rotW.AddKey(time, rot.w);
Vector3 pos = IKTQ.t;
posX.AddKey(time, pos.x);
posY.AddKey(time, pos.y);
posZ.AddKey(time, pos.z);
}
public void SetKeyframes(float time, Vector3 pos, Quaternion rot)
{
// Rotation flipping already prevented in HumanoidBaker.UpdateHumanPose().
rotX.AddKey(time, rot.x);
rotY.AddKey(time, rot.y);
rotZ.AddKey(time, rot.z);
rotW.AddKey(time, rot.w);
posX.AddKey(time, pos.x);
posY.AddKey(time, pos.y);
posZ.AddKey(time, pos.z);
}
public void MoveLastKeyframes(float time)
{
MoveLastKeyframe(time, rotX);
MoveLastKeyframe(time, rotY);
MoveLastKeyframe(time, rotZ);
MoveLastKeyframe(time, rotW);
MoveLastKeyframe(time, posX);
MoveLastKeyframe(time, posY);
MoveLastKeyframe(time, posZ);
}
// Add a copy of the first frame to the specified time
public void SetLoopFrame(float time)
{
BakerUtilities.SetLoopFrame(time, rotX);
BakerUtilities.SetLoopFrame(time, rotY);
BakerUtilities.SetLoopFrame(time, rotZ);
BakerUtilities.SetLoopFrame(time, rotW);
BakerUtilities.SetLoopFrame(time, posX);
BakerUtilities.SetLoopFrame(time, posY);
BakerUtilities.SetLoopFrame(time, posZ);
}
public void SetRootLoopFrame(float time)
{
/*
// TODO Should be based on AnimationClipSettings
BakerUtilities.SetLoopFrame(time, rotX);
BakerUtilities.SetLoopFrame(time, rotY);
BakerUtilities.SetLoopFrame(time, rotZ);
BakerUtilities.SetLoopFrame(time, rotW);
BakerUtilities.SetLoopFrame(time, posY);
*/
}
private void MoveLastKeyframe(float time, AnimationCurve curve)
{
Keyframe[] keys = curve.keys;
keys[keys.Length - 1].time = time;
curve.keys = keys;
}
public void MultiplyLength(AnimationCurve curve, float mlp)
{
Keyframe[] keys = curve.keys;
for (int i = 0; i < keys.Length; i++)
{
keys[i].time *= mlp;
}
curve.keys = keys;
}
// Add curves to the AnimationClip for each channel
public void SetCurves(ref AnimationClip clip, float maxError, float lengthMlp)
{
MultiplyLength(rotX, lengthMlp);
MultiplyLength(rotY, lengthMlp);
MultiplyLength(rotZ, lengthMlp);
MultiplyLength(rotW, lengthMlp);
MultiplyLength(posX, lengthMlp);
MultiplyLength(posY, lengthMlp);
MultiplyLength(posZ, lengthMlp);
BakerUtilities.ReduceKeyframes(rotX, maxError);
BakerUtilities.ReduceKeyframes(rotY, maxError);
BakerUtilities.ReduceKeyframes(rotZ, maxError);
BakerUtilities.ReduceKeyframes(rotW, maxError);
BakerUtilities.ReduceKeyframes(posX, maxError);
BakerUtilities.ReduceKeyframes(posY, maxError);
BakerUtilities.ReduceKeyframes(posZ, maxError);
BakerUtilities.SetTangentMode(rotX);
BakerUtilities.SetTangentMode(rotY);
BakerUtilities.SetTangentMode(rotZ);
BakerUtilities.SetTangentMode(rotW);
/*
BakerUtilities.SetTangentMode(posX);
BakerUtilities.SetTangentMode(posY);
BakerUtilities.SetTangentMode(posZ);
*/
clip.SetCurve(string.Empty, typeof(Animator), Qx, rotX);
clip.SetCurve(string.Empty, typeof(Animator), Qy, rotY);
clip.SetCurve(string.Empty, typeof(Animator), Qz, rotZ);
clip.SetCurve(string.Empty, typeof(Animator), Qw, rotW);
clip.SetCurve(string.Empty, typeof(Animator), Tx, posX);
clip.SetCurve(string.Empty, typeof(Animator), Ty, posY);
clip.SetCurve(string.Empty, typeof(Animator), Tz, posZ);
}
}
// Manages the Animation Curves for a single Transform that is a child of the root Transform.
[System.Serializable]
public class BakerMuscle
{
// Animation curves for each channel of the Transform
public AnimationCurve curve;
private int muscleIndex = -1;
private string propertyName;
// The custom constructor
public BakerMuscle(int muscleIndex)
{
this.muscleIndex = muscleIndex;
this.propertyName = MuscleNameToPropertyName(HumanTrait.MuscleName[muscleIndex]);
Reset();
}
private string MuscleNameToPropertyName(string n)
{
// Left fingers
if (n == "Left Index 1 Stretched") return "LeftHand.Index.1 Stretched";
if (n == "Left Index 2 Stretched") return "LeftHand.Index.2 Stretched";
if (n == "Left Index 3 Stretched") return "LeftHand.Index.3 Stretched";
if (n == "Left Middle 1 Stretched") return "LeftHand.Middle.1 Stretched";
if (n == "Left Middle 2 Stretched") return "LeftHand.Middle.2 Stretched";
if (n == "Left Middle 3 Stretched") return "LeftHand.Middle.3 Stretched";
if (n == "Left Ring 1 Stretched") return "LeftHand.Ring.1 Stretched";
if (n == "Left Ring 2 Stretched") return "LeftHand.Ring.2 Stretched";
if (n == "Left Ring 3 Stretched") return "LeftHand.Ring.3 Stretched";
if (n == "Left Little 1 Stretched") return "LeftHand.Little.1 Stretched";
if (n == "Left Little 2 Stretched") return "LeftHand.Little.2 Stretched";
if (n == "Left Little 3 Stretched") return "LeftHand.Little.3 Stretched";
if (n == "Left Thumb 1 Stretched") return "LeftHand.Thumb.1 Stretched";
if (n == "Left Thumb 2 Stretched") return "LeftHand.Thumb.2 Stretched";
if (n == "Left Thumb 3 Stretched") return "LeftHand.Thumb.3 Stretched";
if (n == "Left Index Spread") return "LeftHand.Index.Spread";
if (n == "Left Middle Spread") return "LeftHand.Middle.Spread";
if (n == "Left Ring Spread") return "LeftHand.Ring.Spread";
if (n == "Left Little Spread") return "LeftHand.Little.Spread";
if (n == "Left Thumb Spread") return "LeftHand.Thumb.Spread";
// Right fingers
if (n == "Right Index 1 Stretched") return "RightHand.Index.1 Stretched";
if (n == "Right Index 2 Stretched") return "RightHand.Index.2 Stretched";
if (n == "Right Index 3 Stretched") return "RightHand.Index.3 Stretched";
if (n == "Right Middle 1 Stretched") return "RightHand.Middle.1 Stretched";
if (n == "Right Middle 2 Stretched") return "RightHand.Middle.2 Stretched";
if (n == "Right Middle 3 Stretched") return "RightHand.Middle.3 Stretched";
if (n == "Right Ring 1 Stretched") return "RightHand.Ring.1 Stretched";
if (n == "Right Ring 2 Stretched") return "RightHand.Ring.2 Stretched";
if (n == "Right Ring 3 Stretched") return "RightHand.Ring.3 Stretched";
if (n == "Right Little 1 Stretched") return "RightHand.Little.1 Stretched";
if (n == "Right Little 2 Stretched") return "RightHand.Little.2 Stretched";
if (n == "Right Little 3 Stretched") return "RightHand.Little.3 Stretched";
if (n == "Right Thumb 1 Stretched") return "RightHand.Thumb.1 Stretched";
if (n == "Right Thumb 2 Stretched") return "RightHand.Thumb.2 Stretched";
if (n == "Right Thumb 3 Stretched") return "RightHand.Thumb.3 Stretched";
if (n == "Right Index Spread") return "RightHand.Index.Spread";
if (n == "Right Middle Spread") return "RightHand.Middle.Spread";
if (n == "Right Ring Spread") return "RightHand.Ring.Spread";
if (n == "Right Little Spread") return "RightHand.Little.Spread";
if (n == "Right Thumb Spread") return "RightHand.Thumb.Spread";
return n;
}
public void MultiplyLength(AnimationCurve curve, float mlp)
{
Keyframe[] keys = curve.keys;
for (int i = 0; i < keys.Length; i++)
{
keys[i].time *= mlp;
}
curve.keys = keys;
}
// Add curves to the AnimationClip for each channel
public void SetCurves(ref AnimationClip clip, float maxError, float lengthMlp)
{
MultiplyLength(curve, lengthMlp);
BakerUtilities.ReduceKeyframes(curve, maxError);
// BakerUtilities.SetTangentMode(curve);
clip.SetCurve(string.Empty, typeof(Animator), propertyName, curve);
}
// Clear all curves
public void Reset()
{
curve = new AnimationCurve();
}
// Record a keyframe for each channel
public void SetKeyframe(float time, float[] muscles)
{
curve.AddKey(time, muscles[muscleIndex]);
}
// Add a copy of the first frame to the specified time
public void SetLoopFrame(float time)
{
BakerUtilities.SetLoopFrame(time, curve);
}
}
//Manages the Animation Curves for a single Transform that is a child of the root Transform.
[System.Serializable]
public class BakerTransform
{
public Transform transform; // The Transform component to record
// Animation curves for each channel of the Transform
public AnimationCurve
posX, posY, posZ,
rotX, rotY, rotZ, rotW;
private string relativePath; // Path relative to the root
private bool recordPosition; // Should we record the localPosition if the transform?
private Vector3 relativePosition;
private bool isRootNode;
private Quaternion relativeRotation;
// The custom constructor
public BakerTransform(Transform transform, Transform root, bool recordPosition, bool isRootNode)
{
this.transform = transform;
this.recordPosition = recordPosition || isRootNode;
this.isRootNode = isRootNode;
relativePath = string.Empty;
#if UNITY_EDITOR
relativePath = UnityEditor.AnimationUtility.CalculateTransformPath(transform, root);
#endif
Reset();
}
public void SetRelativeSpace(Vector3 position, Quaternion rotation)
{
relativePosition = position;
relativeRotation = rotation;
}
// Add curves to the AnimationClip for each channel
public void SetCurves(ref AnimationClip clip, float maxError)
{
if (recordPosition)
{
BakerUtilities.ReduceKeyframes(posX, maxError);
BakerUtilities.ReduceKeyframes(posY, maxError);
BakerUtilities.ReduceKeyframes(posZ, maxError);
clip.SetCurve(relativePath, typeof(Transform), "localPosition.x", posX);
clip.SetCurve(relativePath, typeof(Transform), "localPosition.y", posY);
clip.SetCurve(relativePath, typeof(Transform), "localPosition.z", posZ);
}
BakerUtilities.ReduceKeyframes(rotX, maxError);
BakerUtilities.ReduceKeyframes(rotY, maxError);
BakerUtilities.ReduceKeyframes(rotZ, maxError);
BakerUtilities.ReduceKeyframes(rotW, maxError);
clip.SetCurve(relativePath, typeof(Transform), "localRotation.x", rotX);
clip.SetCurve(relativePath, typeof(Transform), "localRotation.y", rotY);
clip.SetCurve(relativePath, typeof(Transform), "localRotation.z", rotZ);
clip.SetCurve(relativePath, typeof(Transform), "localRotation.w", rotW);
if (isRootNode) AddRootMotionCurves(ref clip);
// @todo probably only need to do it once for the clip
clip.EnsureQuaternionContinuity(); // DOH!
}
private void AddRootMotionCurves(ref AnimationClip clip)
{
if (recordPosition)
{
clip.SetCurve("", typeof(Animator), "MotionT.x", posX);
clip.SetCurve("", typeof(Animator), "MotionT.y", posY);
clip.SetCurve("", typeof(Animator), "MotionT.z", posZ);
}
clip.SetCurve("", typeof(Animator), "MotionQ.x", rotX);
clip.SetCurve("", typeof(Animator), "MotionQ.y", rotY);
clip.SetCurve("", typeof(Animator), "MotionQ.z", rotZ);
clip.SetCurve("", typeof(Animator), "MotionQ.w", rotW);
}
// Clear all curves
public void Reset()
{
posX = new AnimationCurve();
posY = new AnimationCurve();
posZ = new AnimationCurve();
rotX = new AnimationCurve();
rotY = new AnimationCurve();
rotZ = new AnimationCurve();
rotW = new AnimationCurve();
}
public void ReduceKeyframes(float maxError)
{
BakerUtilities.ReduceKeyframes(rotX, maxError);
BakerUtilities.ReduceKeyframes(rotY, maxError);
BakerUtilities.ReduceKeyframes(rotZ, maxError);
BakerUtilities.ReduceKeyframes(rotW, maxError);
BakerUtilities.ReduceKeyframes(posX, maxError);
BakerUtilities.ReduceKeyframes(posY, maxError);
BakerUtilities.ReduceKeyframes(posZ, maxError);
}
// Record a keyframe for each channel
public void SetKeyframes(float time)
{
if (recordPosition)
{
Vector3 pos = transform.localPosition;
if (isRootNode)
{
pos = transform.position - relativePosition;
}
posX.AddKey(time, pos.x);
posY.AddKey(time, pos.y);
posZ.AddKey(time, pos.z);
}
Quaternion rot = transform.localRotation;
if (isRootNode)
{
rot = Quaternion.Inverse(relativeRotation) * transform.rotation;
}
rotX.AddKey(time, rot.x);
rotY.AddKey(time, rot.y);
rotZ.AddKey(time, rot.z);
rotW.AddKey(time, rot.w);
}
// Add a copy of the first frame to the specified time
public void AddLoopFrame(float time)
{
// TODO change to SetLoopFrame
if (recordPosition && !isRootNode)
{
posX.AddKey(time, posX.keys[0].value);
posY.AddKey(time, posY.keys[0].value);
posZ.AddKey(time, posZ.keys[0].value);
}
rotX.AddKey(time, rotX.keys[0].value);
rotY.AddKey(time, rotY.keys[0].value);
rotZ.AddKey(time, rotZ.keys[0].value);
rotW.AddKey(time, rotW.keys[0].value);
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 582052db0a6096149a7b2b75914ef3a6
timeCreated: 1516263395
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,196 @@
using UnityEngine;
using System.Collections;
using UnityEngine.Playables;
namespace RootMotion
{
/// <summary>
/// Baker for Humanoid animation.
/// </summary>
public class HumanoidBaker : Baker
{
/// <summary>
/// Should the hand IK curves be added to the animation? Disable this if the original hand positions are not important when using the clip on another character via Humanoid retargeting.
/// </summary>
[Tooltip("Should the hand IK curves be added to the animation? Disable this if the original hand positions are not important when using the clip on another character via Humanoid retargeting.")]
public bool bakeHandIK = true;
/// <summary>
/// Max keyframe reduction error for the Root.Q/T, LeftFoot IK and RightFoot IK channels. Having a larger error value for 'Key Reduction Error' and a smaller one for this enables you to optimize clip data size without the floating feet effect by enabling 'Foot IK' in the Animator.
/// </summary>
[Tooltip("Max keyframe reduction error for the Root.Q/T, LeftFoot IK and RightFoot IK channels. Having a larger error value for 'Key Reduction Error' and a smaller one for this enables you to optimize clip data size without the floating feet effect by enabling 'Foot IK' in the Animator.")]
[Range(0f, 0.1f)] public float IKKeyReductionError;
/// <summary>
/// Frame rate divider for the muscle curves. If you had 'Frame Rate' set to 30, and this value set to 3, the muscle curves will be baked at 10 fps. Only the Root Q/T and Hand and Foot IK curves will be baked at 30. This enables you to optimize clip data size without the floating feet effect by enabling 'Foot IK' in the Animator.
/// </summary>
[Tooltip("Frame rate divider for the muscle curves. If you have 'Frame Rate' set to 30, and this value set to 3, the muscle curves will be baked at 10 fps. Only the Root Q/T and Hand and Foot IK curves will be baked at 30. This enables you to optimize clip data size without the floating feet effect by enabling 'Foot IK' in the Animator.")]
[Range(1, 9)] public int muscleFrameRateDiv = 1;
private BakerMuscle[] bakerMuscles;
private BakerHumanoidQT rootQT;
private BakerHumanoidQT leftFootQT;
private BakerHumanoidQT rightFootQT;
private BakerHumanoidQT leftHandQT;
private BakerHumanoidQT rightHandQT;
private float[] muscles = new float[0];
private HumanPose pose = new HumanPose();
private HumanPoseHandler handler;
private Vector3 bodyPosition;
private Quaternion bodyRotation = Quaternion.identity;
private int mN = 0;
private Quaternion lastBodyRotation = Quaternion.identity;
void Awake()
{
animator = GetComponent<Animator>();
director = GetComponent<PlayableDirector>();
if (mode == Mode.AnimationStates || mode == Mode.AnimationClips)
{
if (animator == null || !animator.isHuman)
{
Debug.LogError("HumanoidBaker GameObject does not have a Humanoid Animator component, can not bake.");
enabled = false;
return;
}
animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
}
else if (mode == Mode.PlayableDirector)
{
if (director == null)
{
Debug.LogError("HumanoidBaker GameObject does not have a PlayableDirector component, can not bake.");
}
}
muscles = new float[HumanTrait.MuscleCount];
bakerMuscles = new BakerMuscle[HumanTrait.MuscleCount];
for (int i = 0; i < bakerMuscles.Length; i++)
{
bakerMuscles[i] = new BakerMuscle(i);
}
rootQT = new BakerHumanoidQT("Root");
leftFootQT = new BakerHumanoidQT(animator.GetBoneTransform(HumanBodyBones.LeftFoot), AvatarIKGoal.LeftFoot, "LeftFoot");
rightFootQT = new BakerHumanoidQT(animator.GetBoneTransform(HumanBodyBones.RightFoot), AvatarIKGoal.RightFoot, "RightFoot");
leftHandQT = new BakerHumanoidQT(animator.GetBoneTransform(HumanBodyBones.LeftHand), AvatarIKGoal.LeftHand, "LeftHand");
rightHandQT = new BakerHumanoidQT(animator.GetBoneTransform(HumanBodyBones.RightHand), AvatarIKGoal.RightHand, "RightHand");
handler = new HumanPoseHandler(animator.avatar, animator.transform);
}
protected override Transform GetCharacterRoot()
{
return animator.transform;
}
protected override void OnStartBaking()
{
rootQT.Reset();
leftFootQT.Reset();
rightFootQT.Reset();
leftHandQT.Reset();
rightHandQT.Reset();
for (int i = 0; i < bakerMuscles.Length; i++)
{
bakerMuscles[i].Reset();
}
mN = muscleFrameRateDiv;
lastBodyRotation = Quaternion.identity;
}
protected override void OnSetLoopFrame(float time)
{
for (int i = 0; i < bakerMuscles.Length; i++) bakerMuscles[i].SetLoopFrame(time);
rootQT.MoveLastKeyframes(time);
leftFootQT.SetLoopFrame(time);
rightFootQT.SetLoopFrame(time);
leftHandQT.SetLoopFrame(time);
rightHandQT.SetLoopFrame(time);
}
protected override void OnSetCurves(ref AnimationClip clip)
{
float length = bakerMuscles[0].curve.keys[bakerMuscles[0].curve.keys.Length - 1].time;
float lengthMlp = mode != Mode.Realtime ? clipLength / length : 1f;
for (int i = 0; i < bakerMuscles.Length; i++) bakerMuscles[i].SetCurves(ref clip, keyReductionError, lengthMlp);
rootQT.SetCurves(ref clip, IKKeyReductionError, lengthMlp);
leftFootQT.SetCurves(ref clip, IKKeyReductionError, lengthMlp);
rightFootQT.SetCurves(ref clip, IKKeyReductionError, lengthMlp);
if (bakeHandIK)
{
leftHandQT.SetCurves(ref clip, IKKeyReductionError, lengthMlp);
rightHandQT.SetCurves(ref clip, IKKeyReductionError, lengthMlp);
}
}
protected override void OnSetKeyframes(float time, bool lastFrame)
{
// Skip muscle frames
mN++;
bool updateMuscles = true;
if (mN < muscleFrameRateDiv && !lastFrame)
{
updateMuscles = false;
}
if (mN >= muscleFrameRateDiv) mN = 0;
UpdateHumanPose();
if (updateMuscles)
{
for (int i = 0; i < bakerMuscles.Length; i++) bakerMuscles[i].SetKeyframe(time, muscles);
}
rootQT.SetKeyframes(time, bodyPosition, bodyRotation);
Vector3 bodyPositionScaled = bodyPosition * animator.humanScale;
leftFootQT.SetIKKeyframes(time, animator.avatar, animator.transform, animator.humanScale, bodyPositionScaled, bodyRotation);
rightFootQT.SetIKKeyframes(time, animator.avatar, animator.transform, animator.humanScale, bodyPositionScaled, bodyRotation);
leftHandQT.SetIKKeyframes(time, animator.avatar, animator.transform, animator.humanScale, bodyPositionScaled, bodyRotation);
rightHandQT.SetIKKeyframes(time, animator.avatar, animator.transform, animator.humanScale, bodyPositionScaled, bodyRotation);
}
private void UpdateHumanPose()
{
handler.GetHumanPose(ref pose);
bodyPosition = pose.bodyPosition;
bodyRotation = pose.bodyRotation;
bodyRotation = BakerUtilities.EnsureQuaternionContinuity(lastBodyRotation, bodyRotation);
lastBodyRotation = bodyRotation;
for (int i = 0; i < pose.muscles.Length; i++)
{
muscles[i] = pose.muscles[i];
}
}
#if UNITY_EDITOR
protected override void SetClipSettings(AnimationClip clip, UnityEditor.AnimationClipSettings settings)
{
/* v2.0
settings.loopBlendOrientation = true;
settings.loopBlendPositionY = true;
settings.keepOriginalOrientation = true;
settings.keepOriginalPositionY = true;
*/
}
#endif
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: efcc0ec5ed265a340a1412edb9110337
timeCreated: 1516287616
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 15000
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: