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);
            }
        }
        public _Keyframe Clone()
        {
            int       len   = m_Kfs.Length;
            _Keyframe newKf = new _Keyframe(len);

            for (int i = 0; i < len; ++i)
            {
                newKf.m_Kfs[i] = m_Kfs[i];
            }
            return(newKf);
        }
        /// <summary>
        /// create an array of _Keyframe
        /// </summary>
        private void _InitKeyframes()
        {
            m_Keys = new _Keyframe[m_KeyCnt];

            for (int i = 0; i < m_KeyCnt; ++i) //each key
            {
                var k = m_Keys[i] = new _Keyframe(m_Curves.Length);

                for (int cidx = 0; cidx < m_Curves.Length; ++cidx)  //aggregate keys from each curve
                {
                    k.Set(cidx, m_Kf4Curves[cidx][i]);
                }
            }
        }
        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);
        }
        // apply the data from _Keyframe[] to stored AnimationCurve[]
        public void ApplyToUnderlyingCurves()
        {
            for (int curveIdx = 0; curveIdx < m_Curves.Length; ++curveIdx)
            {
                AnimationCurve ac = m_Curves[curveIdx];

                Keyframe[] tmpKeys = new Keyframe[m_KeyCnt];
                for (int keyIdx = 0; keyIdx < m_KeyCnt; ++keyIdx)
                {
                    _Keyframe compKey = m_Keys[keyIdx];
                    tmpKeys[keyIdx] = compKey.Get(curveIdx);
                }

                ac.keys = tmpKeys;
                //Dbg.Log("ac.length = {0}", ac.length);
                AnimationUtility.SetEditorCurve(m_Clip, m_Bindings[curveIdx], ac); //set back
            }
        }
        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);
        }