三次贝塞尔曲线-在给定的X下获得Y-在控制点X不断增加的特殊情况下

时间:2018-08-16 14:47:50

标签: math bezier cubic-bezier

我已经读过a few discussions,有关在三次方贝塞尔曲线的X处找到Y,并且还阅读了此article。 >

我的案件比一般案件更受限制,我想知道是否有比上述讨论中提到的一般方案更好的解决方案。

我的案子:

  • 不同控制点的X正在增加。即: X3 > X2 > X1 > X0
  • 此外,由于上述原因,X(t)也严格单调递增。

有没有一种有效的算法可以将这种限制因素考虑在内?

2 个答案:

答案 0 :(得分:3)

首先,这个答案仅适用于您,因为您的控制点约束意味着我们一直在处理与常规函数等效的参数。在这种情况下,这显然是您想要的,但是将来找到该答案的任何人都应该意识到,这个答案是基于这样的假设,即任何给定的x值都只有一个一个 y值。当然,对于一般的贝塞尔曲线,绝对不是真的

话虽如此,我们知道,即使我们已将该曲线表示为二维的参数曲线,我们仍在处理一条曲线,该曲线对于所有意图和目的都必须具有{{ 1}}。我们也知道,只要我们知道唯一地属于特定x的“ t”值(只有这种情况是因为您严格单调增加系数的属性),我们才能将y计算为y = f(x),因此问题是:给定一些已知的y = By(t)值,我们是否可以计算需要插入t的{​​{1}}值?

答案是:是的,我们可以。

首先,可以说我们开始使用的任何By(t)值都来自x,因此,鉴于我们知道x,我们应该能够找到相应的值x = Bx(t)中的)指向该x

让我们看一下x(t)的函数:

t

我们可以将其重写为简单的多项式形式:

x

这是一个标准的三次多项式,仅将常数称为系数,我们可以轻松地将其重写为:

x(t) = a(1-t)³ + 3b(1-t)²t + 3c(1-t)t² + dt³

您可能想知道“对于所有其他值a,b,c和d,所有-x都放在哪里?”答案是它们都被抵消了,所以我们实际上最终唯一需要减去的就是最后一个。方便!

所以现在我们只需...解决这个问题:除了x(t) = (-a + 3b- 3c + d)t³ + (3a - 6b + 3c)t² + (-3a + 3b)t + a 以外我们都知道,我们只需要一些数学知识即可告诉我们如何执行此操作。

...当然,这里的“限定词”当然不是正确的,找到三次函数的根也没有“正当”的意思,但值得庆幸的是,Gerolano Cardano为确定根的基础奠定了基础在16世纪,使用复数。在任何人还没有发明复数之前。相当壮举!但这是编程的答案,而不是历史的教训,所以让我们开始实现:

给出x的一些已知值,并了解我们的坐标a,b,c和d,我们可以按以下方式实现寻根:

(-a + 3b- 3c + d)t³ + (3a - 6b + 3c)t² + (-3a + 3b)t + (a-x) = 0

好的,那是一段代码,还有很多附加功能:

  • t是cuberoot函数。实际上,在这种情况下,我们实际上并不关心复数,因此更简单的实现方法是使用def,宏,三元数或您选择的语言提供的任何速记形式:// Find the roots for a cubic polynomial with bernstein coefficients // {pa, pb, pc, pd}. The function will first convert those to the // standard polynomial coefficients, and then run through Cardano's // formula for finding the roots of a depressed cubic curve. double[] findRoots(double x, double pa, double pb, double pc, double pd) { double pa3 = 3 * pa, pb3 = 3 * pb, pc3 = 3 * pc, a = -pa + pb3 - pc3 + pd, b = pa3 - 2*pb3 + pc3, c = -pa3 + pb3, d = pa - x; // Fun fact: any Bezier curve may (accidentally or on purpose) // perfectly model any lower order curve, so we want to test // for that: lower order curves are much easier to root-find. if (approximately(a, 0)) { // this is not a cubic curve. if (approximately(b, 0)) { // in fact, this is not a quadratic curve either. if (approximately(c, 0)) { // in fact in fact, there are no solutions. return new double[]{}; } // linear solution: return new double[]{-d / c}; } // quadratic solution: double q = sqrt(c * c - 4 * b * d), b2 = 2 * b; return new double[]{ (q - c) / b2, (-c - q) / b2 }; } // At this point, we know we need a cubic solution, // and the above a/b/c/d values were technically // a pre-optimized set because a might be zero and // that would cause the following divisions to error. b /= a; c /= a; d /= a; double b3 = b / 3, p = (3 * c - b*b) / 3, p3 = p / 3, q = (2 * b*b*b - 9 * b * c + 27 * d) / 27, q2 = q / 2, discriminant = q2*q2 + p3*p3*p3, u1, v1; // case 1: three real roots, but finding them involves complex // maths. Since we don't have a complex data type, we use trig // instead, because complex numbers have nice geometric properties. if (discriminant < 0) { double mp3 = -p/3, r = sqrt(mp3*mp3*mp3), t = -q / (2 * r), cosphi = t < -1 ? -1 : t > 1 ? 1 : t, phi = acos(cosphi), crtr = crt(r), t1 = 2 * crtr; return new double[]{ t1 * cos(phi / 3) - b3, t1 * cos((phi + TAU) / 3) - b3, t1 * cos((phi + 2 * TAU) / 3) - b3 }; } // case 2: three real roots, but two form a "double root", // and so will have the same resultant value. We only need // to return two values in this case. else if (discriminant == 0) { u1 = q2 < 0 ? crt(-q2) : -crt(q2); return new double[]{ 2 * u1 - b3, -u1 - b3 }; } // case 3: one real root, 2 complex roots. We don't care about // complex results so we just ignore those and directly compute // that single real root. else { double sd = sqrt(discriminant); u1 = crt(-q2 + sd); v1 = crt(q2 + sd); return new double[]{u1 - v1 - b3}; } }
  • crt()仅为2π。在进行几何编程时,闲逛是很有用的。
  • crt(x) = x < 0 ? -pow(-x, 1f/3f) : pow(x, 1f/3f);是一种将值与目标周围很小间隔比较的函数,因为IEEE浮点数是 jerks 。基本上,我们是在谈论tau之类的东西。

如果有点java风格的话,其余的应该是不言自明的(我在这类事情上使用Processing)。

使用此实现,我们可以编写实现以在给定x的情况下找到y。与三次调用相比,涉及的内容要多得多,因为立方根是复杂的事物。您最多可以恢复三个根源。但是其中只有一个适用于我们的“ t时间间隔” [0,1]:

approximately

就这样,我们完成了:现在,我们有了“ t”值,可以用来获取相关的“ y”值。

答案 1 :(得分:1)

如果二进制搜索太复杂,仍然有一种O(1)方法,但是它的局限性很大。我假设您使用的是4个控制点(p0(x0,y0),p1(x1,y1),p2(x2,y2),p3(x3,y3))三次贝塞尔曲线,并由间隔t中的某个[0.0 , 1.0]参数化,所以:

t = 0.0 -> x(t) = x0, y(t) = y0;
t = 1.0 -> x(t) = x3, y(t) = y3;

首先让我们暂时忘记贝塞尔曲线,而使用a catmull-rom curve,这只是表示相同曲线的另一种方法。要在2个立方之间转换,请使用以下命令:

// BEzier to Catmull-Rom
const double m=6.0;
X0 = x3+(x0-x1)*m; Y0 = y3+(y0-y1)*m;
X1 = x0;           Y1 = y0;
X2 = x3;           Y2 = y3;
X3 = x0+(x3-x2)*m; Y3 = y0+(y3-y2)*m;

// Catmull-Rom to Bezier
const double m=1.0/6.0;
x0 = X1;           y0 = Y1;
x1 = X1-(X0-X2)*m; y1 = Y1-(Y0-Y2)*m;
x2 = X2+(X1-X3)*m; y2 = Y2+(Y1-Y3)*m;
x3 = X2;           y3 = Y2;

其中(xi,yi)是Bezier控制点,(Xi,Yi)是Catmull-Rom点。现在,如果所有控制点之间的X距离具有相同的距离:

(X3-X2) == (X2-X1) == (X1-X0)

然后X坐标与t线性。这意味着我们可以直接从t计算X

t = (X-X1)/(X2-X1);

现在,我们可以直接为任何Y计算X。因此,如果可以选择控制点,则选择它们以使其符合X距离条件。

如果不满足条件,则可以尝试更改控制点,使其满足要求(通过二进制搜索,将立方细分为更多小块等),但是请注意,更改控制点可能会改变所得曲线的形状如果不小心。