0

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

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

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

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

谢谢

我看到了这个主题:给定 x 三次贝塞尔曲线的 y 坐标

但它会循环,我需要避免循环所以我找到了这个主题:三次贝塞尔曲线 - 为给定 X 获取 Y

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

4

3 回答 3

3

y这在数学上是不可能的,除非您可以保证每个值只有一个值x,即使在单位矩形上您也不能保证(例如,{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 );
}

完成,现在如果您想查找某个x值的y值,只需遍历LUT_y直到找到您的y值,或者更实际地直到您在索引处找到两个值i并且i+1您的y值位于它们之间的某个位置,您将立即知道对应的x值,因为它将在LUT_x.

对于与 2 个索引的非精确匹配,ii+1只需进行线性插值(即y在距离 ... 和 之间i,并且在坐标i+1之间的距离相同)ii+1x

于 2014-10-15T04:03:05.103 回答
3

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

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

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

于 2014-10-15T15:50:50.323 回答
0

再次感谢 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);
于 2014-10-15T05:24:27.480 回答