static void _OptimizeClip(AnimationClip clip) { EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip); // build bone hierachy tree BoneHierarchyTree tree = new BoneHierarchyTree(); tree.Build(bindings); ClipType type = GetClipType(clip.name); for (int i = 0; i < bindings.Length; ++i) { EditorCurveBinding binding = bindings[i]; string boneName = _GetBoneName(binding.path); string boneNameL = boneName.ToLower(); if (boneNameL.Contains("scale")) { if (binding.propertyName.Contains("Rotation") || binding.propertyName.Contains("Position")) { i += _RemoveCurve(clip, binding.path, binding.propertyName); continue; } } else { if (binding.propertyName.Contains("Scale")) { i += _RemoveCurve(clip, binding.path, binding.propertyName); continue; } } // try removing frames using custom error metric bool isRotateCurve = binding.propertyName.Contains("Rotation"); float baseErrorThreshold = 0.001f; if (isRotateCurve) { if (boneName.Contains("Finger") || type == ClipType.Facial) baseErrorThreshold = 0.0001f; else baseErrorThreshold = 0.001f; } else { if (type == ClipType.Facial) baseErrorThreshold = 0.00003f; else baseErrorThreshold = 0.002f; } float errorThreshold = Mathf.Pow(baseErrorThreshold, (Mathf.Lerp(1, 3, tree.GetBoneDistanceToLeaf(boneName)))); if (isRotateCurve) { Debug.Assert(binding.propertyName == "m_LocalRotation.x"); Debug.Assert(i + 3 < bindings.Length); AnimationCurve curveX = AnimationUtility.GetEditorCurve(clip, binding); AnimationCurve curveY = AnimationUtility.GetEditorCurve(clip, bindings[i + 1]); AnimationCurve curveZ = AnimationUtility.GetEditorCurve(clip, bindings[i + 2]); AnimationCurve curveW = AnimationUtility.GetEditorCurve(clip, bindings[i + 3]); if (_RemoveRedundentKeyframesForRotation(binding.path, curveX, curveY, curveZ, curveW, errorThreshold)) { //clip.SetCurve(binding.path, typeof(Transform), "m_LocalRotation", null); AnimationUtility.SetEditorCurve(clip, binding, curveX); AnimationUtility.SetEditorCurve(clip, bindings[i + 1], curveY); AnimationUtility.SetEditorCurve(clip, bindings[i + 2], curveZ); AnimationUtility.SetEditorCurve(clip, bindings[i + 3], curveW); } i += 3; // skip m_LocalRotation.y m_LocalRotation.z m_LocalRotation.w } else { AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding); if (_RemoveRedundentKeyframes(binding.path, curve, errorThreshold)) AnimationUtility.SetEditorCurve(clip, binding, curve); } } } // Interpolate from adjacent keyframes to test if the existing value can be replaced by // an linear interpolated one. static bool _RemoveRedundentKeyframes(string name, AnimationCurve curve, float errorThreshold) { int nextIdx = curve.keys.Length - 1; Keyframe[] kfs = curve.keys; bool newTail = true; for (int i = curve.keys.Length - 2; i >= 1; --i) { Keyframe cur = kfs[i]; Keyframe prev = kfs[i - 1]; Keyframe next = kfs[nextIdx]; float t = (cur.time - prev.time) / (next.time - prev.time); float interpolated = prev.value * t + next.value * (1 - t); if (Mathf.Abs(interpolated - cur.value) < errorThreshold) { if (newTail) { AnimationUtility.SetKeyLeftTangentMode(curve, nextIdx, AnimationUtility.TangentMode.Linear); newTail = false; } curve.RemoveKey(i); AnimationUtility.SetKeyRightTangentMode(curve, i - 1, AnimationUtility.TangentMode.Linear); //curve.SmoothTangents(i - 1, 0); } else { nextIdx = i; newTail = true; } } return curve.keys.Length != kfs.Length; } // Same as _RemoveRedundentKeyframes() // process on quaternion (4 curves)simultaneously // using max euler angle as the error metric(ToAngleAxis() cannot calculate very small value correctly) static bool _RemoveRedundentKeyframesForRotation(string name, AnimationCurve curveX, AnimationCurve curveY, AnimationCurve curveZ, AnimationCurve curveW, float errorThreshold) { int nextIdx = curveX.keys.Length - 1; Keyframe[] kfsX = curveX.keys; Keyframe[] kfsY = curveY.keys; Keyframe[] kfsZ = curveZ.keys; Keyframe[] kfsW = curveW.keys; bool newTail = true; for (int i = curveX.keys.Length - 2; i >= 1; --i) { Keyframe curKf = kfsX[i]; Keyframe prevKf = kfsX[i - 1]; Keyframe nextKf = kfsX[nextIdx]; float t = (curKf.time - prevKf.time) / (nextKf.time - prevKf.time); Quaternion cur = new Quaternion(kfsX[i].value, kfsY[i].value, kfsZ[i].value, kfsW[i].value); Quaternion prev = new Quaternion(kfsX[i - 1].value, kfsY[i - 1].value, kfsZ[i - 1].value, kfsW[i - 1].value); Quaternion next = new Quaternion(kfsX[nextIdx].value, kfsY[nextIdx].value, kfsZ[nextIdx].value, kfsW[nextIdx].value); Quaternion interpolated = Quaternion.Slerp(prev, next, t); Quaternion diff = interpolated * Quaternion.Inverse(cur); Vector3 euler = diff.eulerAngles; //diff.ToAngleAxis(out angle, out axis); if (Mathf.Abs(euler.x) + Mathf.Abs(euler.y) + Mathf.Abs(euler.z) < errorThreshold) { if (newTail) { AnimationUtility.SetKeyLeftTangentMode(curveX, nextIdx, AnimationUtility.TangentMode.Linear); AnimationUtility.SetKeyLeftTangentMode(curveY, nextIdx, AnimationUtility.TangentMode.Linear); AnimationUtility.SetKeyLeftTangentMode(curveZ, nextIdx, AnimationUtility.TangentMode.Linear); AnimationUtility.SetKeyLeftTangentMode(curveW, nextIdx, AnimationUtility.TangentMode.Linear); newTail = false; } curveX.RemoveKey(i); curveY.RemoveKey(i); curveZ.RemoveKey(i); curveW.RemoveKey(i); AnimationUtility.SetKeyRightTangentMode(curveX, i - 1, AnimationUtility.TangentMode.Linear); AnimationUtility.SetKeyRightTangentMode(curveY, i - 1, AnimationUtility.TangentMode.Linear); AnimationUtility.SetKeyRightTangentMode(curveZ, i - 1, AnimationUtility.TangentMode.Linear); AnimationUtility.SetKeyRightTangentMode(curveW, i - 1, AnimationUtility.TangentMode.Linear); } else { nextIdx = i; newTail = true; } } return curveX.keys.Length != kfsX.Length; }