private bool _CanReduce(_AnimCurve curve, _Keyframe key0, _Keyframe key1, float time, float allowedError) { float invT = Mathf.InverseLerp(key0.time, key1.time, time); switch (curve.KeyframeType) { case _AnimCurve.KFType.Position: case _AnimCurve.KFType.Scale: { Vector3 value = curve.Evaluate_Vector3(time); Vector3 v3Key0 = (Vector3)key0; Vector3 v3Key1 = (Vector3)key1; Vector3 interpolatedVal = Vector3.Lerp(v3Key0, v3Key1, invT); return(PositionDistanceError(value, interpolatedVal, allowedError)); } case _AnimCurve.KFType.Rotation: { Quaternion value = curve.Evaluate_Quaternion(time); Quaternion qKey0 = (Quaternion)key0; Quaternion qKey1 = (Quaternion)key1; Quaternion interpolatedVal = Quaternion.Lerp(qKey0, qKey1, invT); return(QuaternionDistanceError(value, interpolatedVal, allowedError)); } default: Dbg.LogErr("AnimCompress._CanReduce: unexpected KFtype: {0}", curve.KeyframeType); return(false); } }
private bool _CanReduce(_Keyframe fromKey, _Keyframe toKey, _AnimCurve curve, float allowedError, float delta, int firstKey, int lastKey, bool useKeyframeLimit) { float beginTime = fromKey.time; float endTime = toKey.time; bool canReduce = true; for (float t = beginTime + delta; t < endTime; t += delta) { if (!(canReduce = _CanReduce(curve, fromKey, toKey, t, allowedError))) { break; } } // we need to check that all keys can be reduced, because keys might be closer to each other than delta // this happens when we have steps in curve // TODO : we could skip the loop above if keyframes are close enough float lastTime = beginTime; for (int j = firstKey; canReduce && j < lastKey; ++j) { float time = curve.Get(j).time; // validates point at keyframe (j) and point between (j) and and (j-1) keyframes // TODO : For checking point at "time" it could just use keys[j].value instead - that would be faster than sampling the curve canReduce = _CanReduce(curve, fromKey, toKey, time, allowedError) && _CanReduce(curve, fromKey, toKey, (lastTime + time) / 2, allowedError); lastTime = time; } if (canReduce) { // validate point between last two keyframes float time = curve.Get(lastKey).time; canReduce = _CanReduce(curve, fromKey, toKey, (lastTime + time) / 2, allowedError); } // Don't reduce if we are about to reduce more than 50 samples at // once to prevent n^2 performance impact canReduce = canReduce && (!useKeyframeLimit || (endTime - beginTime < 50.0F * delta)); return(canReduce); }
private _AnimCurve _GatherScaleCurve(EditorCurveBinding oneBinding) { AnimationCurve[] curves = new AnimationCurve[3]; EditorCurveBinding[] bindings = new EditorCurveBinding[3]; for (int i = 0; i < 3; ++i) { bindings[i] = oneBinding; bindings[i].propertyName = PROP_LSCA + SCA_POSTFIX[i]; curves[i] = AnimationUtility.GetEditorCurve(m_Clip, bindings[i]); if (curves[i] == null) { Dbg.LogWarn("AnimCompress._GatherScaleCurve: failed to get curve on binding: {0}", bindings[i]); return(null); } _RegBinding(bindings[i]); } _AnimCurve newCompCurve = new _AnimCurve(curves, _AnimCurve.KFType.Scale, bindings, m_Clip); return(newCompCurve); }
private float _ReduceKeyFrames(_AnimCurve curve, float sampleRate, float allowedError) { int keycnt = curve.KeyCnt; if (keycnt <= 2) { return(100f); } float delta = 1f / sampleRate; List <_Keyframe> keyLst = new List <_Keyframe>(keycnt); _Keyframe[] keys = curve.keys; _Keyframe kfStart = keys[0].Clone(); _Keyframe kfEnd = keys[keycnt - 1].Clone(); // at first try reducing to const curve kfStart.inTangent = kfStart.outTangent = 0f; kfEnd.inTangent = kfEnd.outTangent = 0f; kfEnd.value = kfStart.value; bool bCanReduceToConstCurve = _CanReduce(kfStart, kfEnd, curve, allowedError, delta, 0, keycnt - 1, false); if (bCanReduceToConstCurve) { keyLst.Add(kfStart); keyLst.Add(kfEnd); } else { keyLst.Capacity = keycnt; keyLst.Add(keys[0]); int lastUsedKey = 0; for (int i = 1; i < keycnt - 1; i++) { _Keyframe fromKey = keys[lastUsedKey]; _Keyframe toKey = keys[i + 1]; //FitTangentsToCurve(curve, fromKey, toKey); bool canReduce = _CanReduce(fromKey, toKey, curve, allowedError, delta, lastUsedKey + 1, i + 1, true); if (!canReduce) { keyLst.Add(keys[i]); // fitting tangents between last two keys //FitTangentsToCurve(curve, *(output.end() - 2), output.back()); lastUsedKey = i; } } // We always add the last key keyLst.Add(keys[keycnt - 1]); // fitting tangents between last and the one before last keys //FitTangentsToCurve(curve, *(output.end() - 2), output.back()); } float compressRatio = (float)keyLst.Count / (float)keycnt * 100.0F; if (m_Verbose) { Dbg.Log("compressRatio = {0}%, {1}/{2}\t{3} : {4}", compressRatio, keyLst.Count, keycnt, curve.Binding0.path, curve.Binding0.propertyName); } curve.keys = keyLst.ToArray(); return(compressRatio); }
public void ReduceKeyframes(AnimationClip clip, float posErr, float rotErr, float scaleErr, float floatErr) { m_Clip = clip; posErr /= 100f; scaleErr /= 100f; floatErr /= 100f; rotErr = Mathf.Cos(rotErr * Mathf.Deg2Rad / 2f); var allCurveData = AnimationUtility.GetAllCurves(clip, false); int curveCnt = m_CurveCnt = allCurveData.Length; m_FinishedCurveCnt = 0; if (curveCnt == 0) { Dbg.LogWarn("There is no curves in given clip: {0}", clip.name); return; } m_checkedCurves = new HashSet <string>(); double time = EditorApplication.timeSinceStartup; int oldKeyCnt = 0; int newKeyCnt = 0; float compRatio = 0f; float sampleRate = clip.frameRate; // execute EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip); foreach (var oneBinding in bindings) { oldKeyCnt += _GetKeyCnt(oneBinding); if (!_HasBinding(oneBinding)) { string pName = oneBinding.propertyName; if (pName.StartsWith(PROP_LPOS)) { // gather all other curves to form a position curve _AnimCurve newCurve = _GatherPosCurve(oneBinding); _ReduceKeyFrames(newCurve, sampleRate, posErr); } else if (pName.StartsWith(PROP_LROT)) { // gather all other curves to form a rotation curve _AnimCurve newCurve = _GatherRotCurve(oneBinding); _ReduceKeyFrames(newCurve, sampleRate, rotErr); } else if (pName.StartsWith(PROP_LSCA)) { // gather all other curves to form a scale curve _AnimCurve newCurve = _GatherScaleCurve(oneBinding); _ReduceKeyFrames(newCurve, sampleRate, scaleErr); } else { Dbg.Log("Ignore curve: {0}", pName); } } newKeyCnt += _GetKeyCnt(oneBinding); m_FinishedCurveCnt++; if (m_FinishedCurveCnt % 10 == 0) { EditorUtility.DisplayProgressBar("Crunching curves...", "Working hard to compress clip..." + m_FinishedCurveCnt + "/" + m_CurveCnt, m_FinishedCurveCnt / (float)m_CurveCnt ); } } EditorUtility.ClearProgressBar(); if (oldKeyCnt != 0) { compRatio = (float)newKeyCnt / (float)oldKeyCnt; } time = EditorApplication.timeSinceStartup - time; Dbg.Log("ReduceKeyFrames: compress ratio: {0:F2}%, time: {1:F4}", compRatio * 100f, time); m_checkedCurves.Clear(); m_checkedCurves = null; }