我有四个点构成一个三次贝塞尔曲线:
P1 = (10, 5)
P2 = (9, 12)
P3 = (24, -2)
P4 = (25, 3)
现在我想找到这条曲线的拐点。我用Google搜索和/但每个人都指的是一个网站:http://www.caffeineowl.com/graphics/2d/vectorial/cubic-inflexion.html
不幸的是applet没有工作,我根本没有把它们放在一起。有人可以如此友善地告诉我如何计算曲线的拐点吗?
答案 0 :(得分:3)
对于Bezier曲线,您有两个参数方程X(t)和Y(t)。 要确定参数曲线的拐点,您需要找到曲线曲率(wiki)改变符号的位置。所以你需要找到上述函数的第一个和第二个导数并求解方程式:
C(t) = X' * Y'' - X'' * Y' = 0
一阶导数是二次的,第二个导数是线性的,因此对于t
,方程式是立方的,最多可以有3个解。
编辑:已阅读相关文章,方程式简化为二次方式,最多可包含2个解决方案。
如果解决方案存在于t范围0..1中,您还必须检查它是否是真正的拐点 - 在此t值处检查C'(t) <> 0
。
示例:蓝色圆圈是拐点(两个捕捉)
实际代码片段(Delphi)
鉴于:
P是控制点阵列
Cf是贝塞尔函数基础的系数
P, Cf: array[0..3] of TPoint
//calculate Bezier coefficients
Cf[3].x := p[3].x - 3 * p[2].x + 3 * p[1].x - p[0].x;
Cf[2].x := 3 * (p[0].x - 2 * p[1].x + p[2].x);
Cf[1].x := 3 * (p[1].x - p[0].x);
Cf[0].x := p[0].x;
//the same for Y
//find parameters of quadratic equation
// a*t^2 + b*t + c = 0
a := 3 * (cf[2].X *cf[3].Y - cf[2].Y *cf[3].X);
b := 3 * (cf[1].X *cf[3].Y - cf[1].Y *cf[3].X);
c := cf[1].X *cf[2].Y - cf[1].Y *cf[2].X;
//here solve quadratic equations, find t parameters
//don't forget a lot of special cases like a=0, D<0, D=0, t outside 0..1 range
Discriminant := b * b - 4 * a * c;
....
答案 1 :(得分:1)
要详细说明MBo的答案,C(t) = X' * Y'' - X'' * Y' = 0
几乎就是你想要的伪代码,因为第一个和第二个导数很容易计算。
关注this explanation of Bezier derivatives,以及坐标为(x1,y1)...(x4,y4)的通用Bezier函数,我们得到:
fx(t) = x1 (1-t)³ + 3·x2·(1-t)²·t + 3·x3·(1-t)·t² + x4·t³
fx'(t) = a·(1-t)² + 2·b·(1-t)·t + c·t²
其中a = 3(x2-x1),b = 3(x3-x2),c = 3(x4-x3),并且:
fx''(t) = u·(1-t) + v·t
其中u = 2(b-a)且v = 2(c-b)。当然,y组件也是如此:
fy(t) = y1 (1-t)³ + 3·y2·(1-t)²·t + 3·y3·(1-t)·t² + y4·t³
fy'(t) = a'·(1-t)² + 2·b'·(1-t)·t + c'·t²
fy''(t) = u'·(1-t) + v'·t
其中a'
与a
相同,但y
值等等。
计算C(t) = fx'(t)·fy''(t) - fx''(t)·fy'(t)
的数学计算很烦人,但这就是我们拥有计算机的原因。如果你拥有树莓派,你拥有Mathematica的许可证,那么让我们使用它:
这是一个庞大的公式,但找到了一个随意的变形&#34;曲线有点傻,因为Bezier曲线对于线性仿射变换是不变的,所以无论我们检查&#34;真实曲线&#34;还是等于拐点的t
值都保持不变。或者我们是否旋转/平移/缩放曲线以使其具有更方便的坐标。就像翻译一样,(x1,y1)最终为(0,0),而(x4,y4)位于x轴上,因此y4为零。
如果我们这样做,那么我们会得到一个非常简单的公式:
这简单多少了?好:
18 times:
- x3 * y2
+ 3 * x3 * y2 * t
- 3 * x3 * y2 * t^2
- x4 * y2 * t
+ 2 * x4 * y2 * t^2
+ x2 * y3
- 3 * x2 * y3 * t
+ 3 * x2 * y3 * t^2
- x4 * y3 * t^2
由于我们正在进行编程,因此有很多可缓存的值。服用:
a = x3 * y2
b = x4 * y2
c = x2 * y3
d = x4 * y3
我们可以将C(t)
简化为:
1/18 * C(t) = -a + 3at - 3at^2 - bt + 2bt^2 + c - 3ct + 3ct^2 - dt^2
= -3at^2 + 2bt^2 + 3ct^2 - dt^2 + 3at - bt - 3ct - a + c
= (-3a + 2b + 3c - d)t^2 + (3a - b - 3c)t + (c - a)
将那个因子18放回去,这只是一个简单的二次公式,我们可以使用二次根同一性和更简单的值来找到根:
v1 = (-3a + 2b + 3c - d) * 18
v2 = (3a - b - 3c) * 18
v3 = (c - a) * 18
而且,如果3a +d
不等于 2b+3c
(因为如果是这样的话,就没有根),我们得到:
sqr = sqrt(v2^2 - 4 * v1 * v3)
d = 2 * v1
root1 = (sqr - v2) / d
root2 = -(sqr + v2) / d
扔掉不会落入Bezier区间[0,1]的根,而你剩下的是原始曲线所适应的t
值。
只是因为我无法将某些东西放在一起,所以我最终得到了一些很好的伪代码。这也是我给出一些点和坐标的原因。我希望看到有人用实数计算这个
特别是在Stackoverflow上,最好不要在前面懒惰,而是承诺可能需要学习新东西。