【问题标题】:Get x on Bezier curve given y给定 y 在贝塞尔曲线上获取 x
【发布时间】:2014-10-15 02:39:02
【问题描述】:

我有一条贝塞尔曲线:(0,0)(.25,.1)(.25,1)(1,1)

此处以图形方式显示:http://cubic-bezier.com/#.25,.1,.25,1

我们在 x 轴上看到的是时间。

这是我的未知数。这是一个单元格。所以我想知道当 y 为 0.5 时如何得到 x?

谢谢

看到了这个话题:y coordinate for a given x cubic bezier

但它会循环,我需要避免循环 于是我找到了这个话题:Cubic bezier curves - get Y for given X

但我不知道如何在 js 中求解三次多项式 :(

【问题讨论】:

    标签: javascript css-transitions bezier cubic


    【解决方案1】:

    这在数学上是不可能的,除非您可以保证每个 x 值只有一个 y 值,即使在单位矩形上您也不能(例如,{0,0},{1, 0.6},{0,0.4},{1,1} 在中间会很有趣!)。最快的方法是简单地构建一个 LUT,例如:

    var LUT_x = [], LUT_y = [], t, a, b, c, d;
    for(let i=0; i<100; i++) {
      t = i/100;
      a = (1-t)*(1-t)*(1-t);
      b = (1-t)*(1-t)*t;
      c = (1-t)*t*t;
      d = t*t*t;
      LUT_x.push( a*x1 + 3*b*x2 + 3*c*x3 + d*x4 );
      LUT_y.push( a*y1 + 3*b*y2 + 3*c*y3 + d*y4 );
    }
    

    完成,现在如果您想查找某个 y 值的 x 值,只需运行 LUT_y 直到找到您的 y 值,或者更实际地直到您在索引处找到两个值 @ 987654331@ 和 i+1 这样您的 y 值介于它们之间,并且您将立即知道相应的 x 值,因为它将在 LUT_x 中的同一索引处。

    对于具有 2 个索引 ii+1 的非精确匹配,您只需进行线性插值(即,y 位于 ii+1 之间的距离 ...,而这与 @ 之间的距离相同987654341@ 和 i+1 用于x 坐标)

    【讨论】:

    • 太糟糕了,所以我们必须循环。非常感谢。我真的很感激所有的帮助。我将它用于 Firefox 插件 Profilist。我需要保持尽可能快的速度,原因之一是:计算是在 Firefox 菜单面板淡入淡出时完成的,做繁重的工作会减慢旧计算机上的速度。我很想向您展示我是如何使用它的,这样您就知道您的努力付诸东流了。您使用 Firefox 吗?
    • 一次性循环很快。之后,您可以简单地使用 O(log(n)) 的二进制搜索。如果您的 LUT 是 100 个位置,那么只有 7 个检查。即使是 1000 个点也只需要 10 次检查。
    • 天哪,没办法,你能告诉我如何在下面的函数中做到这一点,我不知道这个算法!
    • 构建您的 LUT。这是一次 O(n)。然后,如果您需要 y=a,则设置 low=0high=lastmid=(low+high)/2。是mid==a?完毕。如果不是,如果a &lt; mid 我们保持低电平,设置high=midmid=(low+high)/2 并再次检查。如果a &gt; mid 我们保持高电平,设置low=mid,重新计算中间值,然后检查。继续这样做,每一步都会将搜索空间减少 2 倍。所以最坏的情况是,100 个元素在最多 7 个步骤中变为 50、25、13、8、4、2,“找到”。赢家。
    • 这很有趣,这家伙似乎在不使用任何循环的情况下做了一个求解器:freewebs.com/brianjs/ultimateequationsolver.htm 他的代码在这里:freewebs.com/brianjs/js_files/quartic_cubic_quadratic.js 我在此处美化了代码:gist.github.com/Noitidart/563a9f9587719a26ed27
    【解决方案2】:

    所有使用查找表的解决方案只能为您提供近似结果。如果这对你来说足够好,那么你就准备好了。如果您想要更准确的结果,那么您需要使用某种数值方法。

    对于一般的 N 次贝塞尔曲线,您确实需要循环。意思是,您需要使用二分法或 Newton Raphson 方法或类似方法来找到与给定 y 值对应的 x 值,并且此类方法(几乎)总是涉及从初始猜测开始的迭代。如果有多个解决方案,那么您得到的 x 值将取决于您最初的猜测。

    但是,如果您只关心三次贝塞尔曲线,那么解析解是可能的,因为可以使用卡尔达诺公式找到三次多项式的根。在 OP 中引用的此链接 (y coordinate for a given x cubic bezier) 中,Dave Bakker 提供了一个答案,显示了如何使用 Cardano 公式求解三次多项式。提供了 Javascript 源代码。我认为这将是您开始调查的好来源。

    【讨论】:

    • 谢谢,我会将他的方法与我在下面的函数中得到的方法进行比较。
    • 我非常喜欢这个答案,我也在 JS 中实现了算法,在jsbin.com/mutaracihafi/1/edit
    【解决方案3】:

    再次感谢 Mike 的帮助,我们找到了最快的方法。我把这个函数放在一起,平均需要 0.28msg:

    function getValOnCubicBezier_givenXorY(options) {
      /*
      options = {
       cubicBezier: {xs:[x1, x2, x3, x4], ys:[y1, y2, y3, y4]};
       x: NUMBER //this is the known x, if provide this must not provide y, a number for x will be returned
       y: NUMBER //this is the known y, if provide this must not provide x, a number for y will be returned
      }
      */
      if ('x' in options && 'y' in options) {
        throw new Error('cannot provide known x and known y');
      }
      if (!('x' in options) && !('y' in options)) {
        throw new Error('must provide EITHER a known x OR a known y');
      }
    
      var x1 = options.cubicBezier.xs[0];
      var x2 = options.cubicBezier.xs[1];
      var x3 = options.cubicBezier.xs[2];
      var x4 = options.cubicBezier.xs[3];
    
      var y1 = options.cubicBezier.ys[0];
      var y2 = options.cubicBezier.ys[1];
      var y3 = options.cubicBezier.ys[2];
      var y4 = options.cubicBezier.ys[3];
    
      var LUT = {
        x: [],
        y: []
      }
    
      for(var i=0; i<100; i++) {
        var t = i/100;
        LUT.x.push( (1-t)*(1-t)*(1-t)*x1 + 3*(1-t)*(1-t)*t*x2 + 3*(1-t)*t*t*x3 + t*t*t*x4 );
        LUT.y.push( (1-t)*(1-t)*(1-t)*y1 + 3*(1-t)*(1-t)*t*y2 + 3*(1-t)*t*t*y3 + t*t*t*y4 );
      }
    
      if ('x' in options) {
        var knw = 'x'; //known
        var unk = 'y'; //unknown
      } else {
        var knw = 'y'; //known
        var unk = 'x'; //unknown
      }
    
      for (var i=1; i<100; i++) {
        if (options[knw] >= LUT[knw][i] && options[knw] <= LUT[knw][i+1]) {
          var linearInterpolationValue = options[knw] - LUT[knw][i];
          return LUT[unk][i] + linearInterpolationValue;
        }
      }
    
    }
    
    var ease = { //cubic-bezier(0.25, 0.1, 0.25, 1.0)
      xs: [0, .25, .25, 1],
      ys: [0, .1, 1, 1]
    };
    
    var linear = {
      xs: [0, 0, 1, 1],
      ys: [0, 0, 1, 1]
    };
    
    //console.time('calc');
    var x = getValOnCubicBezier_givenXorY({y:.5, cubicBezier:linear});
    //console.timeEnd('calc');
    //console.log('x:', x);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-10
      • 1970-01-01
      • 1970-01-01
      • 2017-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多