【问题标题】:AnimationCurve.Evaluate - Get time by valueAnimationCurve.Evaluate - 按值获取时间
【发布时间】:2014-08-27 12:59:16
【问题描述】:

是否有一种内置方法可以从 Unity3d 中的动画曲线中按值获取时间? (Evaluate的相反方式)

我需要实现这一点(而不是从时间中获得价值):

float time = AnimationCurve.Evaluate(float value);

一般来说,从 Y 值获取 X 值。

【问题讨论】:

  • 这不是内置的,也不一定会导致独特的解决方案。
  • @Bart 好的,有什么方法可以实现吗?也许用相反的键创建新曲线?

标签: unity3d curve


【解决方案1】:

我知道这是 3 岁,但我是通过 Google 搜索找到的,以防其他人登陆:

我只是创建了一条逆曲线,它可以让我按时间查找。

public AnimationCurve speedCurve;

private AnimationCurve inverseSpeedCurve;

private void Start()
{
    //create inverse speedcurve
    inverseSpeedCurve = new AnimationCurve();
    for (int i = 0; i < speedCurve.length; i++)
    {
        Keyframe inverseKey = new Keyframe(speedCurve.keys[i].value, speedCurve.keys[i].time);
        inverseSpeedCurve.AddKey(inverseKey);
    }
}

【讨论】:

  • 迄今为止最好的答案。感谢您抽出宝贵时间就这么老的问题发表这篇文章。
  • 对上述评论进行编辑(五分钟后无法编辑)...实施此操作后,我将降低对此答案的认可强度。为了在所有情况下都能正常工作,需要做更多的工作来反转切线,否则生成的曲线可能看起来非常不同。我现在正在处理这个问题,如果我有有用的东西会在这里发帖。
  • 我发现,如果您以几乎任何有用或有趣的方式使用切线,那么您将很难反转曲线。您将(可能)永远不会得到正确的结果。请看这张图片进行演示和解释:imgur.com/a/4QdhZJ6
【解决方案2】:

只是一个基本的实现,也许它会给你一个想法。方法一直循环,如果您的值当时接近该值,它将产生。这是一个协程,但您可以将其更改为在 Update 中使用吗?

public AnimationCurve curve;
public float valToTime = .5f;
public float treshold = .005f;
public float yourTime;
IEnumerator valueToTime(float determineTime)
{
    float timeCounter = 0;
    Keyframe[] k = curve.keys;
    float endTime = k[k.Length-1].time;
    Debug.Log("end "+endTime);
    while(timeCounter < endTime)
    {
        float val = curve.Evaluate(timeCounter);
        Debug.Log("val "+ val + "  time "+timeCounter);
        // have to find a better solution for treshold sometimes it misses(use Update?)!
        if(Mathf.Abs(val - determineTime) < treshold)
        {
            //Your time would be this
            yourTime = timeCounter;
            yield break;
        }
        else 
        {
            //If it's -1 than a problem occured, try changing treshold
            yourTime = -1f;
        }
        timeCounter += Time.deltaTime;
        yield return null;
    }
}

【讨论】:

  • 到目前为止,这是最好的解决方案。性能不会是最好的,但我的情况是,我只在某些特定情况下需要它。谢谢你的想法。
【解决方案3】:

将发布的大多数解决方案的最佳元素放在一起,我想出了一种可以产生相当高准确性的方法。它涉及预先完成工作,因此也非常有效。

注意: 如果原始曲线具有任何最大/最小点(曲线上梯度为零的点),此方法仍会尝试反转它,但只能通过向倒置曲线引入几个不连续性。它不适合这种情况。

  1. 使用 "sample-delta" 常数在多个 "sample-points" 处评估原始曲线。
  2. 对于每个评估的 "value",将该点的正切计算为 "sample-delta" / "value-delta"
  3. 创建使用 "value" 作为 "time""sample-point" 作为 " 的关键帧value",并将 "inTangent""outTangent" 设置为 Step 3 中获得的切线。
  4. 将每个“采样点”生成的关键帧添加到new AnimationCurve()
  5. new AnimationCurve() 因此是原始版本的反转版本。
  6. 平滑 new AnimationCurve()(反转版本)的切线,以消除因突然而快速的切线变化引起的不连续性。 注意: 如果原始曲线至少有一个最大/最小点,则平滑切线可能会使倒转曲线失去其一般定义。

正态曲线与倒置曲线的图像:

invertedCurve = new AnimationCurve();

float totalTime = normalCurve.keys[normalCurve.length - 1].time;
float sampleX = 0; //The "sample-point"
float deltaX = 0.01f; //The "sample-delta"
float lastY = normalCurve.Evaluate(sampleX);
while (sampleX < totalTime)
{
    float y = normalCurve.Evaluate(sampleX); //The "value"
    float deltaY = y - lastY; //The "value-delta"
    float tangent = deltaX / deltaY;
    Keyframe invertedKey = new Keyframe(y, sampleX, tangent, tangent);
    invertedCurve.AddKey(invertedKey);

    sampleX += deltaX;
    lastY = y;
}
for(int i = 0; i < invertedCurve.length; i++)
{
    invertedCurve.SmoothTangents(i, 0.1f);
}

【讨论】:

    【解决方案4】:

    我刚才非常需要这个东西,所以我想出了这个。我发现它非常准确和快速(准确度值为 10 就足够了,甚至可能更低)。但它只适用于每个值都有一个确定时间的曲线(即,没有像多次具有相同值的波浪那样)。

    与另一个答案类似,它迭代可能的时间 - 但不是以线性方式,步长值从整个时间范围开始,每次减半。

    希望对你有用。

    // NB. Will only work for curves with one definite time for each value
    public float GetCurveTimeForValue( AnimationCurve curveToCheck, float value, int accuracy ) {
    
        float startTime = curveToCheck.keys [0].time;
        float endTime = curveToCheck.keys [curveToCheck.length - 1].time;
        float nearestTime = startTime;
        float step = endTime - startTime;
    
        for (int i = 0; i < accuracy; i++) {
    
            float valueAtNearestTime = curveToCheck.Evaluate (nearestTime);
            float distanceToValueAtNearestTime = Mathf.Abs (value - valueAtNearestTime);
    
            float timeToCompare = nearestTime + step;
            float valueAtTimeToCompare = curveToCheck.Evaluate (timeToCompare);
            float distanceToValueAtTimeToCompare = Mathf.Abs (value - valueAtTimeToCompare);
    
            if (distanceToValueAtTimeToCompare < distanceToValueAtNearestTime) {
                nearestTime = timeToCompare;
                valueAtNearestTime = valueAtTimeToCompare;
            }
            step = Mathf.Abs(step * 0.5f) * Mathf.Sign(value-valueAtNearestTime);
        }
    
        return nearestTime;
    }
    

    【讨论】:

    • 二分搜索方法很聪明,并且使得这个 O(log n)。这是一个比公认的 O(n) 答案更好的答案。感谢您在答案被接受两年后分享。
    【解决方案5】:

    我自己偶然发现了这个问题并且不喜欢这里提到的解决方案,所以我想分享我自己的。它是对反转关键帧的答案的适应。 我还通过反转切线和点的权重来改进它。 我确信有一种更简单的方法,但我发现这可以很好地反转动画曲线。

    编辑:忘了提,对我来说,它只在切线设置为加权时才有效,我不知道当你将它设置为自动或类似时,权重计算统一会做什么,所以加权是可预测的并且易于反转。

                inverseCurve = new AnimationCurve();
            for (int i = 0; i < initialCurve.length; i++)
            {
                float inWeight = (initialCurve.keys[i].inTangent * initialCurve.keys[i].inWeight) / 1;
                
                float outWeight = (initialCurve.keys[i].outTangent * initialCurve.keys[i].outWeight) / 1;
                
                Keyframe inverseKey = new Keyframe(initialCurve.keys[i].value, initialCurve.keys[i].time, 1/initialCurve.keys[i].inTangent, 1/initialCurve.keys[i].outTangent, inWeight, outWeight);
                
                inverseCurve.AddKey(inverseKey);
            }
    

    【讨论】:

    • 为什么在inWeight rsp 的计算结束时除以/ 1outWeight?两个产品都是浮点数,类型是浮点数 - 那个除法根本不做任何事情吗?
    • 是的,他们什么都不做,我认为他们只是我在查看内部文档时添加的一种精神上的东西。不能肯定地说,虽然我的想法已经有一段时间了。
    【解决方案6】:

    我想我会分享我自己的版本,正如其他论坛中所建议的那样,我尝试循环遍历 Evaluate() 而不是反转整个曲线,我认为这有点矫枉过正而且并不总是可行的。

    这会检查近似值到指定的小数点,它还假设曲线具有“标准化”时间(如果不是这种情况,可以通过查找最小和最大时间键来扩展。

    /// <summary>
    /// Inverse of Evaluate()
    /// </summary>
    /// <param name="curve">normalized AnimationCurve (time goes from 0 to 1)</param>
    /// <param name="value">value to search</param>
    /// <returns>time at which we have the closest value not exceeding it</returns>
    public static float EvaluateTime(this AnimationCurve curve, float value, int decimals = 6) {
        // Retrieve the closest decimal and then go down
        float time = 0.1f;
        float step = 0.1f;
        float evaluate = curve.Evaluate(time);
        while(decimals > 0) {
            // Loop until we pass our value
            while(evaluate < value) {
                time += step;
                evaluate = curve.Evaluate(time);
            }
    
            // Go one step back and increase precision of the step by one decimal
            time -= step;
            evaluate = curve.Evaluate(time);
            step /= 10f;
            decimals--;
        }
    
        return time;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多