我有一条贝塞尔曲线:
(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 中求解三次多项式:(
这在数学上是不可能的,除非您可以保证每个
y
值只有一个 x
值,即使在单位矩形上也无法保证(例如,{0,0},{1,0.6} ,{0,0.4},{1,1} 在中间点会相当有趣!)。
我们可以象征性地解决这个问题,正如https://pomax.github.io/bezierinfo/#yforx上所解释的那样,使用Cardanos算法找到我们的x(t)函数的根,但是如果你我们将要做“大量”曲线工作,简单地构建一个相对较小的 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
坐标)(但是当然:如果您想要精度,请实现卡尔达诺算法,或者只是将其从底漆复制粘贴到贝塞尔曲线上,然后计算精确值)
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);