我有一条贝塞尔曲线:(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中求解三次多项式:(
答案 0 :(得分: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
和{{1}之间} {和i+1
坐标i
和i+1
之间的距离相同
答案 1 :(得分:3)
使用查找表的所有解决方案只能为您提供近似结果。如果这对你来说足够好,你就定了。如果您想要更准确的结果,那么您需要使用某种数值方法。
对于N度的一般Bezier曲线,您需要循环。意思是,您需要使用双截面方法或Newton Raphson方法或类似的方法来查找对应于给定y值的x值,并且此类方法(几乎)总是涉及从初始猜测开始的迭代。如果有多个解决方案,那么你获得的x值将取决于你的初始猜测。
但是,如果您只关心三次贝塞尔曲线,则可以使用卡尔达诺公式找到三次多项式的根,因此可以使用解析解。在OP中引用的这个链接(y coordinate for a given x cubic bezier)中,Dave Bakker的答案显示了如何使用Cardano公式求解三次多项式。提供Javascript中的源代码。我认为这将是您开始调查的良好来源。
答案 2 :(得分: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);