Получить х на кривой Безье, заданной у
У меня есть кривая Безье: (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);