AnimationCurve - Provide the value to find the time?

Hi, I think the question is pretty straightforward

Is it possible to find the time of an AnimationCurve that correspond to a known value between 0 and 1 ?

Albeit there is no documented functions to do that, maybe some maths can help but I can’t figure out how

Thanks

Your curve need to be strictly monotonic to be able to do that. Otherwise, there could be multi times with the same value. But if it is the case and that your curve is juste piecewise line, you can reverse the curve :

static AnimationCurve ReverseCurve(AnimationCurve c)
{
    // TODO: check c is strictly monotonic and Piecewise linear, log error otherwise
    var rev = new AnimationCurve();
    for(int i=0;i<c.keys.Length;i++) {
        var kf=c.keys*;*

var rkf = new Keyframe(kf.value,kf.time);
if(kf.inTangent < 0) {
rkf.inTangent = 1/kf.outTangent;
rkf.outTangent = 1/kf.inTangent;
} else {
rkf.inTangent = 1/kf.inTangent;
rkf.outTangent = 1/kf.outTangent;
}
rev.AddKey(rkf);
}
return rev;
}
It will not work with non piecewise linear curve. In that case, I guess you’ll have to use something like a simple bisection method or a fancier Newton’s method.
Here is the code for bisection method :
static float TimeFromValue(AnimationCurve c, float value, float precision = 1e-6f)
{
float minTime = c.keys[0].time;
float maxTime = c.keys[c.keys.Length-1].time;
float best = (maxTime + minTime) / 2;
float bestVal = c.Evaluate(best);
int it=0;
const int maxIt = 1000;
float sign = Mathf.Sign(c.keys[c.keys.Length-1].value -c.keys[0].value);
while(it < maxIt && Mathf.Abs(minTime - maxTime) > precision) {
if((bestVal - value) * sign > 0) {
maxTime = best;
} else {
minTime = best;
}
best = (maxTime + minTime) / 2;
bestVal = c.Evaluate(best);
it++;
}
return best;
}

If you are using an animation curve in your script, you can just access it through the Evaluate() method.

like so:

public AnimationCurve anim;
public float curveIndex;
public float curveValue;


void Update()
{
    curveIndex = Mathf.Clamp(curveIndex, 0, curve.Length);
    curveValue = anim.Evaluate(curveIndex);
}

the above code will give you a value “curveValue” in the inspector that is the value of the curve at time “curveIndex”.

I had faced similar Problem, I was using the curve with 2 key frames

  • one starts at the 0th location of Graph
  • next at 20 towards y axis(Value) “20”. 100,000 towards X axis(Time)

the in build API in animation curve Evaluate only based on time to value and this simple trick will help you to find the data other way around

Just loop the time and use the Evaluate until you find your desired Value.

code Example for my problem

float time = 0;
float value = 10.0f; 
public AnimationCurve levelCurve;

IEnumerator RequiredTime(){
while(true){
time +=100;
var sceond = levelCurve.Evaluate(time);
if(sceond > value + 1f) { 
 Debug.Log(time+" the Timerequired to next level up");  break;
 }
}			
yield return null;
}

my value was 10 and i want to find the time taken to reach value 11. so that I can use it in my game as a feed back to player the time or progress required to reach next Stage or so.

this is just the sample code for the logic.

I’m also on this topic as of now I have something that works, but feel that it’s not as optimal as I’d like it to be, here is my simple code so far. (btw I have only to keys and only care about the second one so its hard coded like that feel free to change it as you need)

private int GetLevelByXP(float experience)
{
    int level = 1;

    for (int i = 0; i < xpReqruirmentPerLevel.keys[1].time; i++)
    {
        if (Expirience < xpReqruirmentPerLevel.Evaluate(i))
        {
            level = i;
            break;
        }

    }

    return level;
}

Here’s the code that I use for my curve level progression which is kinda optimized for frequent calls. The answer to OP is only 6 lines of code in the EvaluateLevel method which also works for curves with multiple keys. Note that this will break with curves that somehow dips in the middle (which will produce duplicate values at different time), but that should be a considered a design error.

[Serializable]
public class CurveLevelProgression : ILevelProgression
{
    [Tooltip("\"time\" is level and \"value\" is progression at that level")]
    [SerializeField] private AnimationCurve _curve;

    private int? _maxLevel;

    public int MaxLevel
    {
        get
        {
            _maxLevel ??= (_curve == null || _curve.length == 0) ? 0 : (int)_curve[_curve.length - 1].time;
            return _maxLevel.Value;
        }
    }

    public float EvaluateProgress(int level) => _curve.Evaluate(level);

    public int EvaluateLevel(float progress)
    {
        if (MaxLevel < 1 || progress < EvaluateProgress(1)) { return 0; }
        for (int level = MaxLevel; level >= 0; level--)
        {
            if (EvaluateProgress(level) <= progress) { return level; }
        }
        return 0;
    }
}
Sanity check code
public class LevelProgression : MonoBehaviour
{
    [SerializeField] private int _level;
    [SerializeField] private float _progress;

    [SerializeField] private CurveLevelProgression _curveLevelProgression;

    private void OnValidate()
    {
        Debug.Log($"Curve| EvaluateProgress(level:{_level}):{_curveLevelProgression.EvaluateProgress(_level)}| EvaluateLevel(progress:{_progress}):{_curveLevelProgression.EvaluateLevel(_progress)}");
    }
}

Higher level count would probably require binary search for performance, but that’s another topic.

Also, you shouldn’t use AnimationCurve.keys[i] like @govladi did because it allocates a new array every .keys call. Use AnimationCurve[i] instead, and AnimationCurve.length for key count.