/// lhsIndex and rhsIndex are the indices in the keys array. The lhsIndex/rhsIndex may be -1, in which it creates a synthetic first key /// at startTime, or beyond the length of the array, in which case it creates a synthetic key at endTime. static private Keyframe EvalKeyAtTime([DisallowNull] NativeArray <Keyframe> keys, int lhsIndex, int rhsIndex, float startTime, float endTime, float currTime) { var lhsKey = KeyframeUtility.FetchKeyFromIndexClampEdge(keys, lhsIndex, startTime, endTime); var rhsKey = KeyframeUtility.FetchKeyFromIndexClampEdge(keys, rhsIndex, startTime, endTime); float currValue; float currDeriv; KeyframeUtility.EvalCurveSegmentAndDeriv(out currValue, out currDeriv, lhsKey, rhsKey, currTime); return(new Keyframe(currTime, currValue, currDeriv, currDeriv)); }
/// <summary> /// Interpolates two AnimationCurves. Since both curves likely have control points at different places /// in the curve, this method will create a new curve from the union of times between both curves. However, to avoid creating /// garbage, this function will always replace the keys of lhsAndResultCurve with the final result, and return lhsAndResultCurve. /// </summary> /// <param name="lhsAndResultCurve">The start value. Additionaly, this instance will be reused and returned as the result.</param> /// <param name="rhsCurve">The end value.</param> /// <param name="t">The interpolation factor in range [0,1].</param> static public void InterpAnimationCurve(ref AnimationCurve lhsAndResultCurve, [DisallowNull] AnimationCurve rhsCurve, float t) { if (t <= 0.0f || rhsCurve.length == 0) { // no op. lhsAndResultCurve is already the result } else if (t >= 1.0f || lhsAndResultCurve.length == 0) { // In this case the obvous solution would be to return the rhsCurve. BUT (!) the lhsCurve and rhsCurve are different. This function is // called by: // stateParam.Interp(stateParam, toParam, interpFactor); // // stateParam (lhsCurve) is a temporary in/out parameter, but toParam (rhsCurve) might point to the original component, so it's unsafe to // change that data. Thus, we need to copy the keys from the rhsCurve to the lhsCurve instead of returning rhsCurve. KeyframeUtility.ResetAnimationCurve(lhsAndResultCurve); for (int i = 0; i < rhsCurve.length; i++) { lhsAndResultCurve.AddKey(rhsCurve[i]); } lhsAndResultCurve.postWrapMode = rhsCurve.postWrapMode; lhsAndResultCurve.preWrapMode = rhsCurve.preWrapMode; } else { // Note: If we reached this code, we are guaranteed that both lhsCurve and rhsCurve are valid with at least 1 key // create a native array for the temp keys to avoid GC var lhsCurveKeys = new NativeArray <Keyframe>(lhsAndResultCurve.length, Allocator.Temp); var rhsCurveKeys = new NativeArray <Keyframe>(rhsCurve.length, Allocator.Temp); for (int i = 0; i < lhsAndResultCurve.length; i++) { lhsCurveKeys[i] = lhsAndResultCurve[i]; } for (int i = 0; i < rhsCurve.length; i++) { rhsCurveKeys[i] = rhsCurve[i]; } float startTime = Mathf.Min(lhsCurveKeys[0].time, rhsCurveKeys[0].time); float endTime = Mathf.Max(lhsCurveKeys[lhsAndResultCurve.length - 1].time, rhsCurveKeys[rhsCurve.length - 1].time); // we don't know how many keys the resulting curve will have (because we will compact keys that are at the exact // same time), but in most cases we will need the worst case number of keys. So allocate the worst case. int maxNumKeys = lhsAndResultCurve.length + rhsCurve.length; int currNumKeys = 0; var dstKeys = new NativeArray <Keyframe>(maxNumKeys, Allocator.Temp); int lhsKeyCurr = 0; int rhsKeyCurr = 0; while (lhsKeyCurr < lhsCurveKeys.Length || rhsKeyCurr < rhsCurveKeys.Length) { // the index is considered invalid once it goes off the end of the array bool lhsValid = lhsKeyCurr < lhsCurveKeys.Length; bool rhsValid = rhsKeyCurr < rhsCurveKeys.Length; // it's actually impossible for lhsKey/rhsKey to be uninitialized, but have to // add initialize here to prevent compiler erros var lhsKey = new Keyframe(); var rhsKey = new Keyframe(); if (lhsValid && rhsValid) { lhsKey = GetKeyframeAndClampEdge(lhsCurveKeys, lhsKeyCurr); rhsKey = GetKeyframeAndClampEdge(rhsCurveKeys, rhsKeyCurr); if (lhsKey.time == rhsKey.time) { lhsKeyCurr++; rhsKeyCurr++; } else if (lhsKey.time < rhsKey.time) { // in this case: // rhsKey[curr-1].time <= lhsKey.time <= rhsKey[curr].time // so interpolate rhsKey at the lhsKey.time. rhsKey = KeyframeUtility.EvalKeyAtTime(rhsCurveKeys, rhsKeyCurr - 1, rhsKeyCurr, startTime, endTime, lhsKey.time); lhsKeyCurr++; } else { // only case left is (lhsKey.time > rhsKey.time) Assert.IsTrue(lhsKey.time > rhsKey.time); // this is the reverse of the lhs key case // lhsKey[curr-1].time <= rhsKey.time <= lhsKey[curr].time // so interpolate lhsKey at the rhsKey.time. lhsKey = KeyframeUtility.EvalKeyAtTime(lhsCurveKeys, lhsKeyCurr - 1, lhsKeyCurr, startTime, endTime, rhsKey.time); rhsKeyCurr++; } } else if (lhsValid) { // we are still processing lhsKeys, but we are out of rhsKeys, so increment lhs and evaluate rhs lhsKey = GetKeyframeAndClampEdge(lhsCurveKeys, lhsKeyCurr); // rhs will be evaluated between the last rhs key and the extrapolated rhs key at the end time rhsKey = KeyframeUtility.EvalKeyAtTime(rhsCurveKeys, rhsKeyCurr - 1, rhsKeyCurr, startTime, endTime, lhsKey.time); lhsKeyCurr++; } else { // either lhsValid is True, rhsValid is True, or they are both True. So to miss the first two cases, // right here rhsValid must be true. Assert.IsTrue(rhsValid); // we still have rhsKeys to lerp, but we are out of lhsKeys, to increment rhs and evaluate lhs rhsKey = GetKeyframeAndClampEdge(rhsCurveKeys, rhsKeyCurr); // lhs will be evaluated between the last lhs key and the extrapolated lhs key at the end time lhsKey = KeyframeUtility.EvalKeyAtTime(lhsCurveKeys, lhsKeyCurr - 1, lhsKeyCurr, startTime, endTime, rhsKey.time); rhsKeyCurr++; } var dstKey = KeyframeUtility.LerpSingleKeyframe(lhsKey, rhsKey, t); dstKeys[currNumKeys] = dstKey; currNumKeys++; } // Replace the keys in lhsAndResultCurve with our interpolated curve. KeyframeUtility.ResetAnimationCurve(lhsAndResultCurve); for (int i = 0; i < currNumKeys; i++) { lhsAndResultCurve.AddKey(dstKeys[i]); } dstKeys.Dispose(); } }