using System; using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; [CustomEditor(typeof(AnimationHelper))] public class AnimationHelperInspector : Editor { public enum CurveType {POS_X, POS_Y, POS_Z, ROT_X, ROT_Y, ROT_Z }; const float segLen = 0.1f; const float timeStep = 0.02f; private const float handleSize = 0.04f; private const float pickSize = 0.06f; //private Transform trans; private AnimationHelper aHelper; class PositionData : IComparable { public PositionData() { //xKeyId = yKeyId = zKeyId = -1; point = Vector3.zero; inTan = Vector3.zero; outTan = Vector3.zero; } public Vector3 point; public Vector3 inTan; public Vector3 outTan; public float time; //public int xKeyId, yKeyId, zKeyId; public int CompareTo(PositionData other) { if (time < other.time) return -1; else if (time > other.time) return 1; return 0; } } class AnimationData { SortedList keys = new SortedList(); public Transform trans; public int Count { get { return keys.Count; } } public PositionData this[int i] { get { return keys.Values[i]; } set { keys.Values[i] = value; } } public void GenerateKeys(AnimationCurve curve) { for (int i = 0; i < curve.keys.Length; i++) { Keyframe k = curve.keys[i]; if (!keys.ContainsKey(k.time)) { PositionData p = new PositionData(); p.time = k.time; keys.Add(k.time, p); } } } public void CopyKeys(AnimationCurve curve, CurveType type) { foreach (Keyframe k in curve.keys) { PositionData data = keys[k.time]; switch (type) { case CurveType.POS_X: data.point.x = k.value; data.inTan.x = k.inTangent; data.outTan.x = k.outTangent; break; case CurveType.POS_Y: data.point.y = k.value; data.inTan.y = k.inTangent; data.outTan.y = k.outTangent; break; case CurveType.POS_Z: data.point.z = k.value; data.inTan.z = k.inTangent; data.outTan.z = k.outTangent; break; } } } public bool AddMissingKeysToCurve(AnimationCurve curve) { bool res = false; //curve.k KeyTimeComparer cmp = new KeyTimeComparer(); Keyframe k = new Keyframe(); for (int i = 0; i < keys.Values.Count; i++) { k.time = keys.Values[i].time; if (Array.BinarySearch(curve.keys, k, cmp) < 0) { curve.AddKey(k.time, curve.Evaluate(k.time)); res = true; } } return res; } public void TransformToWorld() { if (trans) { foreach (KeyValuePair k in keys) { k.Value.inTan = trans.TransformDirection(k.Value.inTan); k.Value.outTan = trans.TransformDirection(k.Value.outTan); k.Value.point = trans.TransformPoint(k.Value.point); } } for (int i = 0; i < keys.Count - 1; i++) { float a = 1f / 3f; float timeDiff = this[i + 1].time - this[i].time; a *= timeDiff; this[i].outTan *= a; this[i + 1].inTan *= a; } } public Vector3 GetWorldPosition(int idx) { return trans ? trans.TransformPoint(this[idx].point) : this[idx].point; } public void SetWorldPosition(int idx, Vector3 pos) { if (keys.Count <= idx) return; this[idx].point = trans ? trans.InverseTransformPoint(pos) : pos; } public Vector3 GetWorldTangent(int idx) { Vector3 res = Vector3.zero; return res; } public bool InTangentFromWorld(int idx, Vector3 point, out Vector3 outVect) { outVect = point; if (idx - 1 >= 0) { point = this[idx].point - point; float timeDiff = this[idx].time - this[idx - 1].time; float a = 1f / 3.0f; a *= timeDiff; point /= a; if (trans) point = trans.transform.InverseTransformDirection(point); outVect = point; } else { return false; } return true; } public bool OutTangentFromWorld(int idx, Vector3 point, out Vector3 outVect) { outVect = point; if (idx + 1 < keys.Count) { point = point - this[idx].point; float timeDiff = this[idx + 1].time - this[idx].time; float a = 1f / 3.0f; a *= timeDiff; point /= a; if (trans) point = trans.transform.InverseTransformDirection(point); outVect = point; } else { return false; } return true; } } AnimationData animData; // List positionAnim; AnimationCurve pxCurve; AnimationCurve pyCurve; AnimationCurve pzCurve; Nullable pxBind = null; Nullable pyBind = null; Nullable pzBind = null; AnimationClip anim; class KeyTimeComparer : System.Collections.Generic.IComparer { public int Compare(Keyframe x, Keyframe y) { return (x).time.CompareTo(y.time); } } private bool AddMissingKeysToCurve(AnimationCurve src, AnimationCurve dest) { bool res = false; KeyTimeComparer cmp = new KeyTimeComparer(); for (int i=0; i(dest.keys, src[i], cmp) < 0) { dest.AddKey(src[i].time, dest.Evaluate(src[i].time)); res = true; } } return res; } private void EnsureKeysIntegrity() { /* bool xChanged = false; bool yChanged = false; bool zChanged = false; yChanged |= AddMissingKeysToCurve(pxCurve, pyCurve); zChanged |= AddMissingKeysToCurve(pxCurve, pzCurve); xChanged |= AddMissingKeysToCurve(pyCurve, pxCurve); zChanged |= AddMissingKeysToCurve(pyCurve, pzCurve); xChanged |= AddMissingKeysToCurve(pzCurve, pxCurve); yChanged |= AddMissingKeysToCurve(pzCurve, pyCurve); if (xChanged) AnimationUtility.SetEditorCurve(anim, pxBind.Value, pxCurve); if (yChanged) AnimationUtility.SetEditorCurve(anim, pyBind.Value, pyCurve); if (zChanged) AnimationUtility.SetEditorCurve(anim, pzBind.Value, pzCurve); */ if (animData.AddMissingKeysToCurve(pxCurve)) AnimationUtility.SetEditorCurve(anim, pxBind.Value, pxCurve); if (animData.AddMissingKeysToCurve(pyCurve)) AnimationUtility.SetEditorCurve(anim, pyBind.Value, pzCurve); if (animData.AddMissingKeysToCurve(pzCurve)) AnimationUtility.SetEditorCurve(anim, pzBind.Value, pzCurve); } private bool FindBindings() { Animator animator = null; String path = ""; Transform trans = aHelper.transform; while (true) { animator = trans.GetComponent(); if (animator != null || trans.parent == null) { break; } else { path = path == "" ? trans.gameObject.name : trans.gameObject.name + "/" + path; trans = trans.parent; } } Debug.Log("Current Path: " + path); if (animator == null) return false; //find clip if (aHelper.currentClip != null) { anim = aHelper.currentClip; } else { AnimatorClipInfo[] info = animator.GetCurrentAnimatorClipInfo(0); if (info.Length == 0) animator.GetNextAnimatorClipInfo(0); if (info.Length == 0) return false; anim = info[0].clip; aHelper.currentClip = anim; } EditorCurveBinding[] cb = AnimationUtility.GetCurveBindings(anim); //AnimationUtility.g pxBind = null; pyBind = null; pzBind = null; //trans = aHelper.transform; foreach (EditorCurveBinding b in cb) { //Debug.Log("Property: " + b.propertyName + ", Path:" + b.path); if (b.path != path) continue; if (b.propertyName == "m_LocalPosition.x") { pxBind = b; } else if (b.propertyName == "m_LocalPosition.y") { pyBind = b; } else if (b.propertyName == "m_LocalPosition.z") { pzBind = b; } } return pxBind.HasValue && pyBind.HasValue && pzBind.HasValue; } private void OnSceneGUI() { aHelper = target as AnimationHelper; animData = new AnimationData(); //use transformy for points only if parent exists. Otherwise, keys are in global coords. if (aHelper.transform.parent) animData.trans = aHelper.transform.parent.transform; if (!FindBindings()) return; pxCurve = null; pyCurve = null; pzCurve = null; if (pxBind.HasValue) { pxCurve = AnimationUtility.GetEditorCurve(anim, pxBind.Value); } if (pyBind.HasValue) { pyCurve = AnimationUtility.GetEditorCurve(anim, pyBind.Value); } if (pzBind.HasValue) { pzCurve = AnimationUtility.GetEditorCurve(anim, pzBind.Value); } // anim. if (pxCurve == null || pyCurve == null || pzCurve == null) return; //positionAnim = new List(); animData.GenerateKeys(pxCurve); animData.GenerateKeys(pyCurve); animData.GenerateKeys(pzCurve); if (animData.Count < 2) return; EnsureKeysIntegrity(); animData.CopyKeys(pxCurve, CurveType.POS_X); animData.CopyKeys(pyCurve, CurveType.POS_Y); animData.CopyKeys(pzCurve, CurveType.POS_Z); animData.TransformToWorld(); for (int i = 0; i < animData.Count; i++) { ShowPoint(i); } for (int i = 0; i < animData.Count - 1; i++) { Handles.DrawBezier(animData[i].point, animData[i + 1].point, animData[i].point + animData[i].outTan, animData[i + 1].point - animData[i + 1].inTan, Color.white, null, 2f); } /* PositionData tmp; for (int i = 0; i < pxCurve.keys.Length; i++) { Keyframe k = pxCurve.keys[i]; tmp = new PositionData(); tmp.time = k.time; int idx = positionAnim.BinarySearch(tmp); if (idx < 0) { //tmp.xKeyId = i; tmp.point.x = k.value; tmp.inTan.x = k.inTangent; tmp.outTan.x = k.outTangent; positionAnim.Add(tmp); } else { //positionAnim[idx].xKeyId = i; positionAnim[idx].point.x = k.value; positionAnim[idx].inTan.x = k.inTangent; positionAnim[idx].outTan.x = k.outTangent; } } for (int i = 0; i < pyCurve.keys.Length; i++) { Keyframe k = pyCurve.keys[i]; tmp = new PositionData(); tmp.time = k.time; int idx = positionAnim.BinarySearch(tmp); if (idx < 0) { //tmp.yKeyId = i; tmp.point.y = k.value; tmp.inTan.y = k.inTangent; tmp.outTan.y = k.outTangent; positionAnim.Add(tmp); } else { //positionAnim[idx].yKeyId = i; positionAnim[idx].point.y = k.value; positionAnim[idx].inTan.y = k.inTangent; positionAnim[idx].outTan.y = k.outTangent; } } for (int i = 0; i < pzCurve.keys.Length; i++) { Keyframe k = pzCurve.keys[i]; tmp = new PositionData(); tmp.time = k.time; int idx = positionAnim.BinarySearch(tmp); if (idx < 0) { //tmp.zKeyId = i; tmp.point.z = k.value; tmp.inTan.z = k.inTangent; tmp.outTan.z = k.outTangent; positionAnim.Add(tmp); } else { //positionAnim[idx].zKeyId = i; positionAnim[idx].point.z = k.value; positionAnim[idx].inTan.z = k.inTangent; positionAnim[idx].outTan.z = k.outTangent; } } if (positionAnim.Count < 2) return; for (int i = 0; i < positionAnim.Count; i++) { if (trans.parent) { positionAnim[i].point = trans.parent.transform.TransformPoint(positionAnim[i].point); positionAnim[i].inTan = trans.parent.transform.TransformDirection(positionAnim[i].inTan); positionAnim[i].outTan = trans.parent.transform.TransformDirection(positionAnim[i].outTan); } ShowPoint(i); } // Debug.Log(anim.frameRate); //adjust tangents for (int i = 0; i < positionAnim.Count - 1; i++) { float a = 1f/3f; float timeDiff = positionAnim[i + 1].time - positionAnim[i].time; a *= timeDiff; positionAnim[i].outTan *= a; positionAnim[i + 1].inTan *= a; } for (int i = 0; i < positionAnim.Count - 1; i++) { Handles.DrawBezier(positionAnim[i].point, positionAnim[i + 1].point, positionAnim[i].point + positionAnim[i].outTan, positionAnim[i + 1].point - positionAnim[i + 1].inTan, Color.white, null, 2f); } float timeEnd = positionAnim[positionAnim.Count - 1].time; Vector3 p0 = positionAnim[0].point; Vector3 p1 = p0; for (float t = positionAnim[0].time; t < timeEnd; t += timeStep) { p1.x = pxCurve.Evaluate(t); p1.y = pyCurve.Evaluate(t); p1.z = pzCurve.Evaluate(t); // p1 = trans.TransformPoint(p1); Handles.DrawLine(p0, p1); p0 = p1; } */ } private Vector3 ShowPoint(int index) { PositionData pData = animData[index]; Vector3 point = pData.point; float size = HandleUtility.GetHandleSize(point); if (index == 0) { size *= 2f; } Handles.color = Color.gray; Quaternion handleRot = Tools.pivotRotation == PivotRotation.Local ? aHelper.transform.rotation : Quaternion.identity; //Handles.DrawBezier(positionAnim[i].point, positionAnim[i + 1].point, positionAnim[i].point + positionAnim[i].outTan, positionAnim[i + 1].point - positionAnim[i + 1].inTan, Color.white, null, 2f); Handles.DrawLine(point, point - pData.inTan); Handles.DrawLine(point, point + pData.outTan); if (Handles.Button(point, handleRot, size * handleSize, size * pickSize, Handles.DotHandleCap)) { aHelper.selectedKeyIndex = index; aHelper.selectionType = AnimationHelper.SelectionType.KEY; Repaint(); } else if (Handles.Button(point + pData.outTan, handleRot, size * handleSize, size * pickSize, Handles.DotHandleCap)) { aHelper.selectedKeyIndex = index; aHelper.selectionType = AnimationHelper.SelectionType.OUT_TANGENT; Repaint(); } else if (Handles.Button(point - pData.inTan, handleRot, size * handleSize, size * pickSize, Handles.DotHandleCap)) { aHelper.selectedKeyIndex = index; aHelper.selectionType = AnimationHelper.SelectionType.IN_TANGENT; Repaint(); } if (aHelper.selectedKeyIndex == index) { EditorGUI.BeginChangeCheck(); switch (aHelper.selectionType) { case AnimationHelper.SelectionType.KEY: point = Handles.DoPositionHandle(point, handleRot); break; case AnimationHelper.SelectionType.IN_TANGENT: point = Handles.DoPositionHandle(point - pData.inTan, handleRot); break; case AnimationHelper.SelectionType.OUT_TANGENT: point = Handles.DoPositionHandle(point + pData.outTan, handleRot); break; } if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(aHelper, "Move Point"); EditorUtility.SetDirty(aHelper); Keyframe[] xkeys, ykeys, zkeys; xkeys = pxCurve.keys; ykeys = pyCurve.keys; zkeys = pzCurve.keys; switch (aHelper.selectionType) { case AnimationHelper.SelectionType.KEY: animData.SetWorldPosition(index, point); point = animData[index].point; xkeys[index].value = point.x; ykeys[index].value = point.y; zkeys[index].value = point.z; break; case AnimationHelper.SelectionType.IN_TANGENT: if (animData.InTangentFromWorld(index, point, out point)) { xkeys[index].inTangent = point.x; ykeys[index].inTangent = point.y; zkeys[index].inTangent = point.z; // if (aHelper.tangentMode == AnimationHelper.TangentMode.SMOOTH) { xkeys[index].outTangent = point.x; ykeys[index].outTangent = point.y; zkeys[index].outTangent = point.z; } } break; case AnimationHelper.SelectionType.OUT_TANGENT: if (animData.OutTangentFromWorld(index, point, out point)) { xkeys[index].outTangent = point.x; ykeys[index].outTangent = point.y; zkeys[index].outTangent = point.z; // if (aHelper.tangentMode == AnimationHelper.TangentMode.SMOOTH) { xkeys[index].inTangent = point.x; ykeys[index].inTangent = point.y; zkeys[index].inTangent = point.z; } } break; } pxCurve.keys = xkeys; pyCurve.keys = ykeys; pzCurve.keys = zkeys; AnimationUtility.SetEditorCurve(anim, pxBind.Value, pxCurve); AnimationUtility.SetEditorCurve(anim, pyBind.Value, pyCurve); AnimationUtility.SetEditorCurve(anim, pzBind.Value, pzCurve); } } return point; } /* private Vector3 ShowPoint(int index) { PositionData pData = positionAnim[index]; Vector3 point = pData.point; float size = HandleUtility.GetHandleSize(point); if (index == 0) { size *= 2f; } Handles.color = Color.gray; //Handles.DrawBezier(positionAnim[i].point, positionAnim[i + 1].point, positionAnim[i].point + positionAnim[i].outTan, positionAnim[i + 1].point - positionAnim[i + 1].inTan, Color.white, null, 2f); Handles.DrawLine(point, point - pData.inTan); Handles.DrawLine(point, point + pData.outTan); if (Handles.Button(point, trans.localRotation, size * handleSize, size * pickSize, Handles.DotCap)) { aHelper.selectedKeyIndex = index; aHelper.selectionType = AnimationHelper.SelectionType.KEY; Repaint(); } else if (Handles.Button(point + pData.outTan, trans.localRotation, size * handleSize, size * pickSize, Handles.DotCap)) { aHelper.selectedKeyIndex = index; aHelper.selectionType = AnimationHelper.SelectionType.OUT_TANGENT; Repaint(); } else if (Handles.Button(point - pData.inTan, trans.localRotation, size * handleSize, size * pickSize, Handles.DotCap)) { aHelper.selectedKeyIndex = index; aHelper.selectionType = AnimationHelper.SelectionType.IN_TANGENT; Repaint(); } if (aHelper.selectedKeyIndex == index) { EditorGUI.BeginChangeCheck(); switch (aHelper.selectionType) { case AnimationHelper.SelectionType.KEY: point = Handles.DoPositionHandle(point, trans.localRotation); break; case AnimationHelper.SelectionType.IN_TANGENT: point = Handles.DoPositionHandle(point - pData.inTan, trans.localRotation); break; case AnimationHelper.SelectionType.OUT_TANGENT: point = Handles.DoPositionHandle(point + pData.outTan, trans.localRotation); break; } if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(aHelper, "Move Point"); EditorUtility.SetDirty(aHelper); Keyframe[] xkeys, ykeys, zkeys; xkeys = pxCurve.keys; ykeys = pyCurve.keys; zkeys = pzCurve.keys; switch (aHelper.selectionType) { case AnimationHelper.SelectionType.KEY: if (trans.parent) point = trans.parent.transform.InverseTransformPoint(point); xkeys[index].value = point.x; ykeys[index].value = point.y; zkeys[index].value = point.z; break; case AnimationHelper.SelectionType.IN_TANGENT: point = pData.point - point; if (index -1 >= 0) { float timeDiff = pData.time - positionAnim[index-1].time; float a = 1f/3.0f; a *= timeDiff; point /= a; if (trans.parent) point = trans.parent.transform.InverseTransformDirection(point); xkeys[index].inTangent = point.x; ykeys[index].inTangent = point.y; zkeys[index].inTangent = point.z; // if (aHelper.tangentMode == AnimationHelper.TangentMode.SMOOTH) { xkeys[index].outTangent = point.x; ykeys[index].outTangent = point.y; zkeys[index].outTangent = point.z; } } break; case AnimationHelper.SelectionType.OUT_TANGENT: point = point - pData.point; if (index + 1 < positionAnim.Count) { float timeDiff = positionAnim[index + 1].time - pData.time; float a = 1f/3f; a *= timeDiff; point /= a; if (trans.parent) point = trans.parent.transform.InverseTransformDirection(point); xkeys[index].outTangent = point.x; ykeys[index].outTangent = point.y; zkeys[index].outTangent = point.z; // if (aHelper.tangentMode == AnimationHelper.TangentMode.SMOOTH) { xkeys[index].inTangent = point.x; ykeys[index].inTangent = point.y; zkeys[index].inTangent = point.z; } } break; } pxCurve.keys = xkeys; pyCurve.keys = ykeys; pzCurve.keys = zkeys; AnimationUtility.SetEditorCurve(anim, pxBind.Value, pxCurve); AnimationUtility.SetEditorCurve(anim, pyBind.Value, pyCurve); AnimationUtility.SetEditorCurve(anim, pzBind.Value, pzCurve); } } return point; } */ private float GetBezierValue(float t, float p0, float p1, float p2, float p3) { t = Mathf.Clamp01(t); float oneMinusT = 1.0f - t; float oneMinusT2 = oneMinusT * oneMinusT; float oneMinusT3 = oneMinusT2 * oneMinusT; float t2 = t * t; float t3 = t2 * t; return oneMinusT3 * p0 + 3.0f * oneMinusT2 * t * p1 + 3.0f * oneMinusT * t2 * p2 + t3 * p3; } private float GetBezierDerivative(float t, float p0, float p1, float p2, float p3) { t = Mathf.Clamp01(t); float oneMinusT = 1f - t; return 3f * oneMinusT * oneMinusT * (p1 - p0) + 6f * oneMinusT * t * (p2 - p1) + 3f * t * t * (p3 - p2); } }