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;
        }