Получить х на кривой Безье, заданной у

У меня есть кривая Безье: (0,0), (.25,.1), (.25,1), а также (1,1),

Это наглядно видно здесь: http://cubic-bezier.com/

Мы видим на оси X время.

Это мой неизвестный. Это элементарная ячейка. Поэтому мне было интересно, как я могу получить х, когда у 0,5?

Спасибо

Я видел эту тему: координата у для данного кубического Безье

Но он зацикливается, мне нужно что-то зацикливать, поэтому я нашел эту тему: Кубические кривые Безье - получить Y для данного X

Но я не могу понять, как решить кубический полином в js:(

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 ​​индексами i а также i+1 вы просто делаете линейную интерполяцию (т.е. y находится на расстоянии... между i а также i+1и это на том же расстоянии между i а также i+1 для x координаты)

Все решения, использующие справочную таблицу, могут дать вам только приблизительный результат. Если это достаточно хорошо для вас, вы настроены. Если вы хотите более точный результат, то вам нужно использовать какой-то числовой метод.

Для общей кривой Безье степени N вам необходимо выполнить цикл. Это означает, что вам нужно использовать метод сечения, метод Ньютона-Рафсона или что-то подобное, чтобы найти значение x, соответствующее заданному значению y, и такие методы (почти) всегда включают в себя итерации, начиная с первоначального предположения. Если есть несколько решений, то какое значение x вы получите, будет зависеть от вашего первоначального предположения.

Однако, если вы заботитесь только о кубических кривых Безье, то аналитическое решение возможно, так как корни кубических полиномов можно найти с помощью формулы Кардано. В этой ссылке ( координата y для данного x кубического Безье), на которую ссылается OP, есть ответ Дейва Баккера, который показывает, как решить кубический полином с помощью формулы Кардано. Исходные коды в Javascript предоставляются. Я думаю, что это будет вашим хорошим источником для начала вашего расследования.

Еще раз благодаря помощи Майка мы нашли самый быстрый способ сделать это. Я поставил эту функцию вместе, занимает в среднем 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);
Другие вопросы по тегам