给定 y 得到贝塞尔曲线上的 x

问题描述 投票:0回答:3

我有一条贝塞尔曲线:

(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 中求解三次多项式:(

javascript css-transitions bezier cubic
3个回答
3
投票

这在数学上是不可能的,除非您可以保证每个

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
坐标)
(但是当然:如果您想要精度,请实现卡尔达诺算法,或者只是将其从底漆复制粘贴到贝塞尔曲线上,然后计算精确值)


3
投票

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

但是,如果您只关心三次贝塞尔曲线,那么解析解是可能的,因为可以使用卡尔达诺公式找到三次多项式的根。在OP中引用的这个链接(给定x三次贝塞尔曲线的

y坐标

)中,Dave Bakker给出了一个答案,展示了如何使用卡尔达诺公式求解三次多项式。提供了 Javascript 源代码。我认为这将是您开始调查的良好来源。


0
投票

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);

© www.soinside.com 2019 - 2024. All rights reserved.