社区
数据结构与算法
帖子详情
牛人请进,贝塞尔曲线生成算法
tuxw
2003-12-16 03:31:02
转了好几个论坛,找不到答案
给定一条贝塞尔曲线的四(一个起点,两个控制点,一个终点)个关键点,在VC下可以用 PolyBezier 生成这条曲线,请问 PolyBezier 是如何实现的?就是怎样用(只能用) moveto、lineto 来生成这条曲线?
...全文
941
22
打赏
收藏
牛人请进,贝塞尔曲线生成算法
转了好几个论坛,找不到答案 给定一条贝塞尔曲线的四(一个起点,两个控制点,一个终点)个关键点,在VC下可以用 PolyBezier 生成这条曲线,请问 PolyBezier 是如何实现的?就是怎样用(只能用) moveto、lineto 来生成这条曲线?
复制链接
扫一扫
分享
转发到动态
举报
写回复
配置赞助广告
用AI写文章
22 条
回复
切换为时间正序
请发表友善的回复…
发表回复
打赏红包
flying520520
2003-12-29
打赏
举报
回复
好!
tuxw
2003-12-19
打赏
举报
回复
谢谢,我忽略了这个问题,最小精度实际应该是1个逻辑单位,比1小后是没意义了
下面是我现在的可以控制递归深度的测试函数
int myPolyBezierToEx( CDC *pDC, const POINT p0, const POINT p1,
const POINT p2, const POINT p3,
int depth )
{
long double d1 = PointToLine2( p1, p0, p3 );
long double d2 = PointToLine2( p2, p0, p3 );
if( d1 < d2 ) d1 = d2;
if( d1 < 1.0001 || depth <= 0 ) {
pDC->LineTo( p3 );
return depth;
}
// 折分曲线
...
myPolyBezierToEx( pDC, p0, p11, p22, p33, depth-1 );
myPolyBezierToEx( pDC, p33, p32, p31, p3, depth-1 );
}
理论上来,递归越深,生成的曲线越平滑,这一点在画椭圆、圆等比较规则的曲线时是符合的,我将那个判断点距的条件去掉,递归到10层以上时,速度的优势就基本表现不出来了。
但在画字体轮廓时(由多段短曲线构成),一般递归4、5层就比较好了,加上点距条件,观察返回结果,发现递归的层恰好也在4、5层左右,但如果去掉点距条件,强行递归更深层次,效果反而越来越差,对此很是不解????
开始想可能是坐标不能为小数,导致递归一定数后,那个取中点的算法已经在原地踏步了,但这样应该只是牺牲一时间,不应该带来曲线质量变化?还是因为我的逻辑精度远高于设备精度,导致显示器无法表现?我要拿这算法来做控制的,现在只是在显示器上模拟,具体效果还有待考证……
谢谢大家的指点,本帖结了,请继续关注我的下一个问题:
http://expert.csdn.net/Expert/topic/2569/2569601.xml?temp=.2605249
(不规则区域填充算法)
tuxw
2003-12-18
打赏
举报
回复
我测试了,前面说的内凹点确实由连p1p2引起的,直接连p0p3后好了很多(个别地方还是有),但生成的曲线圆滑度明显不够,这就迫使我给定更小的误差值,这又取决求点到直线距离的精度,用我上面帖出的函数,那个比较误差最小为0.9,再小程序一运行就退出了,估计进入了无限递归。
你上面给的求点到直线的方法,因为要求 ∠PtP1P2,可能仍然要用 atan (目前我只想到这种方法,就是求tp1,p1p2与X轴夹角之差:P)
下面是我改求距离平方的函数
long double DistancePointToLine2( const POINT pt, const POINT p1, const POINT p2 )
{
long double a1 = atan( long double(p2.y-p1.y)/(p2.x-p1.x) );
long double a2 = atan( long double(pt.y-p1.y)/(pt.x-p1.x) );
// sin(∠PtP1P2)
long double t = sin( fabs( a1 - a2 ) );
// DistanceOfTwoPoints2 是点距平方
// [ |PtP1| * sin(∠PtP1P2) ] ^ 2
return DistanceOfTwoPoints2( pt, p1 ) * t * t;
}
现在误差可以小到0.8
可是效果还是差强人意,总是没有连p1p2那么平滑
另外,E 比1小的话,求距离平方比直接求距离趋向于 E 的速度要快,从而减小递归次,是否会因此导致误差精度上的损失呢?
saint001
2003-12-18
打赏
举报
回复
对于每一个t,都可以
Pi(j) = pi(j-1)*t + pi-1(j-1)*(1-t)=t*(pi(j-1)-pi-1(j-1))+pi-1(j-1)
递推得到p(t),应该是递推
就是对每个t,应用我最开始的那个回复进行递推计算
不过这样还是逐点运算,但CAGD强调线性运算和稳定性更重些,用到PC机上不知道他们是不是用这个算法
你的那个递归,实际上是用Decast算法算出t=1/2的值,然后再分段,每次只计算t=1/2的值
优点是可能有的地方t要算得深一些(密),有的地方浅一些,但这样又增加了判断递归结束的花销。
如果采用同等深度的计算,可以加上递归层数限制,比如超过10层就结束,这实际上精度就固定为1/2^10,跟逐点运算就是一样了。
dengsf
2003-12-18
打赏
举报
回复
我对计算几何的东西不熟悉,上面的建议都是乱来的,有更好的方法请指点。
而那个生成曲线的方法是在某段语言晦涩的书里找出来的,可能领略不了某些精妙的思想,从而导致表述的时候有所遗漏……不清楚啊不清楚!
TO saint001(saint001) :
请问“德卡算法”是什么?
“……对所有的t进行递归……”,我转过来的“只对t=1/2递归”是什么回事?
请问能否介绍一下德卡算法,并结合该例子说明一下。可以吗?
dengsf
2003-12-18
打赏
举报
回复
TO tuxw(醉书生):
嗯,用到中点距离代替到线段距离确实不行,前者趋向0的速度可能会比后者的慢很多。我想得不全面……
个人感觉,当误差比较小的时候,直接连p0p3,应该会比 p0p1p2p3的折线段要好一些。
因为前者明显地要比后者要光滑很多;
而且,P1,P2本来只是控制点,实际生成的曲线到这两点还是有一定差距的;既然两者都有差距,那还不如用形式较简单的POP3来代替~~不过具体怎样就不清楚了。
你说的图形放大和圆弧上看到的内凹现象,可能是因为P0P1P2P3的折线段造成的,不知直接用POP3线段代替的效果如何?
上面应该还可以再做一些优化,我说说我的建议:
1、输入的 E 是允许误差的平方,这样求距离的时候,可以直接用距离的平方跟 E 进行比较,免去 sqrt 的运算;
2、求点到直线的距离,用atan好像太浪费时间了~~我说说我的想法:
设直线上的两点是 p1,p2,想求线段外的点 pt 到该直线的距离的“平方”(跟你程序的命名方式一样),可以这样:
(pt.x - p1.x)^2 + (pt.y - p1.y)^2
- [(pt.x-p1.x)(p2.x-p1.x) + (pt.y-p1.y)(p2.y-p1.y)]^2 / [(p1.x - p2.x)^2 + (p1.y - p2.y)^2]
第一行是 pt到p1 的距离的平方。
第二行是求线段在 p1p2 上投影的线段的长度的平方。分子等于 [|p1p2|*|p1pt|*cos(<ptp1p2)]^2(<表示角度符号^ ^),分母是 |p1p2|^2。相除就得到投影距离的平方了~~
整个式子用勾股定理求出距离的“平方”,跟第一个建议结合起来,应该可以免去了sqrt,atan,cos等神奇的运算了。
txw2
2003-12-18
打赏
举报
回复
上面打错了,我的意思是没有就结帖了
连续回复不能超过3次,只好再注册个ID :P
tuxw
2003-12-18
打赏
举报
回复
进一步测试发现,在几何图形(特别是在较大的圆和椭圆)的表现上,前面那个函数效果明显要好,用递归的方法无法完全消除凸圆弧上某些点内凹的现象
公式法
x = (1-t)^3 *x0 + 3*t*(1-t)^2 *x1 + 3*t^2*(1-t) *x2 + t^3 *x3
y = (1-t)^3 *y0 + 3*t*(1-t)^2 *y1 + 3*t^2*(1-t) *y2 + t^3 *y3
递归法
Pi(j) = [Pi(j-1) + P(i-1)(j-1)] / 2
我认为公式法应该是比较通用的方法,效果应该最接近的,特别是在给定曲线两端点距离较大的弧线表现很好,递归法的优点是速度快,但只适合比较短或较长但弯曲得不厉害的曲线
再等一等,看还有没有更好的方法,如果有,就结贴了
dengsf
2003-12-18
打赏
举报
回复
to saint001(saint001):
原来那就是Decast算法,你上面已经说了的~~~谢谢……
根据你给的那个递推式子应该可以证明那些结论了。
to tuxw(醉书生) :
我的意思是这样来算 点pt 到 直线 p1p2 的距离的 “平方”:
long double DistancePointToLine2( const POINT pt, const POINT p1, const POINT p2 )
{
/*
d(pt, p1p2)^2 =
(pt.x - p1.x)^2 + (pt.y - p1.y)^2
- [(pt.x-p1.x)(p2.x-p1.x) + (pt.y-p1.y)(p2.y-p1.y)]^2 / [(p1.x - p2.x)^2 + (p1.y - p2.y)^2]
*/
int ptx_p1x = pt.x - p1.x;
int pty_p1y = pt.y - p1.y;
int p2x_p1x = p2.x - p1.x;
int p2y_p1y = p2.y - p1.y;
//p1->pt距离平方
double d_p1pt_2 = ptx_p1x * ptx_p1x + pty_p1y * pty_p1y;
//p1pt 跟 p1p2 的点积的平方
double dj_p1pt_p1p2_2 = (ptx_p1x*p2x_p1x + pty_p1y*p2y_p1y) * (ptx_p1x*p2x_p1x + pty_p1y*p2y_p1y);
//p1->p2的距离平方
double d_p1p2_2 = p1x_p2x*p1x_p2x + p1y_p2y*p1y_p2y;
return d_p1pt_2 - dj_p1pt_p1p2_2 / d_p1p2_2;
}
用平方来比较误差就是,假设你原来希望这样来判断误差:
d(pt,p0p3) < E
现在改为判断 d(pt,p0p3)^2 < E^2.
这跟原来的效果基本相同吧。
d(pt,p0p3)^2 在上面的求距离函数中已给出了,
E^2,可以在运行时先算一次,又或者将指定的值认为就是 误差的平方。
至于误差不能太小,我想因为"点的坐标是整数"而引起的,也可能是精度丢失造成的,我没有深入钻研过你的程序,所以上面是胡乱猜测而已。
实际上,误差E表示的是 以象素为单位 的误差,设得太小好像意义不大:)
tuxw
2003-12-17
打赏
举报
回复
To dengsf(杀神大菜鸟):
/*
不过德卡算法对每个t都进行递推运算,而不是只用t=1/2再加上递归
Pi(j) = pi(j-1)*t + pi-1(j-1)*(1-t)
*/
请问 t 的递归是怎样的,谢谢
tuxw
2003-12-17
打赏
举报
回复
TO : dengsf(杀神大菜鸟)
我试了你的方法,发现以到中点的距离平方来判断不行,因为可能一条曲本身很接近直线,但端点距离很远,会增加加递归的次数,且对往回绕的曲线误差较大,有些地方会出现平滑的圆弧上某些点往内凹。
另外很意外的是按上面的方法不递归,直接对后面的三点 LineTo,用来描述字体时已经比较近了,我又试着手工递归了三层,效果基本上很好了,只是放大后在很圆的地方会看到明显的折线段。
下面是我现在采用的版本,还是求了点到直线的距离,速度还是很快,我的视图是要用鼠标滚轮来缩放的,用视的 OnDraw 函数中用上面贴的那个函数时,每滚一下滚轮,需要1、2秒来刷新,放大倍数高时耗时更长,而下面这个版本鼠标可以连续滚动,仅仅是视图有些闪烁,
我试着用VC提供的 PolyBezierTo 生成曲线跟它重叠显示,放大到一千倍左右,基本上还能重合,只有少数总分有少量出入,所以我觉得这个算法还是可行的。
POINT CenterOfTwoPoints( POINT p1, POINT p2)
{
POINT center;
center.x = (p1.x + p2.x) >> 1;
center.y = (p1.y + p2.y) >> 1;
return center;
}
double DistanceOfTwoPoints( const POINT p1, const POINT p2 )
{
return sqrt( (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y) );
}
double DistancePointToLine( const POINT pt, const POINT p1, const POINT p2 )
{
double a1 = atan( double(p2.y-p1.y)/(p2.x-p1.x) );
double a2 = atan( double(pt.y-p1.y)/(pt.x-p1.x) );
a1 = 3.14159265/2 - fabs( a1 - a2 );
return fabs( DistanceOfTwoPoints( pt, p1 ) * cos(a1) );
}
void myPolyBezierTo( CDC *pDC, const POINT p0, const POINT p1,
const POINT p2, const POINT p3 )
{
double d1 = DistancePointToLine( p1, p0, p3 );
double d2 = DistancePointToLine( p2, p0, p3 );
if( d1 < d2 ) d1 = d2;
if( d1 < 1.00002 ) {
pDC->LineTo( p1 );
pDC->LineTo( p2 );
pDC->LineTo( p3 ); // 本来只要这句就可以了,但加上前两句精度更好一些
return;
}
// 使用公式 P(i)(j) = [p(i)(j-1) + p(i-1)(j-1)] / 2
// 将原曲线分成两条曲线:( p00, p11, p22, p33 ) , ( p33, p32, p31, p30 )
// P00
// P10 P11
// P20 P21 P22
// P30 P31 P32 P33
POINT p11, p21, p22, p31, p32, p33;
p11 = CenterOfTwoPoints(p1, p0);
p21 = CenterOfTwoPoints(p2, p1);
p22 = CenterOfTwoPoints(p21, p11);
p31 = CenterOfTwoPoints(p3, p2);
p32 = CenterOfTwoPoints(p31, p21);
p33 = CenterOfTwoPoints(p32, p22);
myPolyBezierTo( pDC, p0, p11, p22, p33 );
myPolyBezierTo( pDC, p33, p32, p31, p3 );
}
dengsf
2003-12-17
打赏
举报
回复
唉,老是这样,一不小心就按错了。继续:
对 P0,P1,P2,P3 4个点,先作如下判断:
如果 max( d(p1, p0p3), d(p2, p0p3) ) < E (d()是点到直线距离,E是给定的误差范围,表示p0,p2,p3,p3的凸包的边跟生成曲线的最大差距。凸包我不知道是什么,可能是这四个点所能组成的最大的凸四边形吧。)
如果 满足 条件,则简单用线段 P0P3 代替生成的曲线(因为P0P3 也在其生成的凸包里面,所以其误差也一定小于E)。退出。
如果不满足,则作如下运算:
P0(0)
P1(0) P1(1)
P2(0) P2(1) P2(2)
P3(0) P3(1) P3(2) P3(3)
其中 Pi(0) = Pi
Pi(j) = [pi(j-1) + pi-1(j-1)] / 2
可以证明,
p3(3)在原来的曲线上面,
以 p0(0), p1(1), p2(2), p3(3) 生成的BEZIER曲线跟 原来生成的曲线的P0 - P3(3)那部分完全重合;
P3(3),p3(2),P3(1),P3(0) 生成的BEZIER曲线跟 原来生成的曲线的 P3(3) - P3 那部分完全重合。
这样,用这 8 个点生成两段曲线 来代替原来的做法,并对 每 4 个点也重复上面的过程~~~~
感觉这样的空间利用比较多,应该还可以再进行一些代码级的优化。如果用汇编来编写,则效率可能更高。
dengsf
2003-12-17
打赏
举报
回复
我找了一下,发现实际应用时是通过递归逼近来实现的,我说说我看到的大概做法:
对 P0,P1,P2,P3
saint001
2003-12-17
打赏
举报
回复
dengsf(杀神大菜鸟):
你说的类似那个德卡斯特里奥算法
不过德卡算法对每个t都进行递推运算,而不是只用t=1/2再加上递归
Pi(j) = pi(j-1)*t + pi-1(j-1)*(1-t)
dengsf
2003-12-17
打赏
举报
回复
是笔误,不好意思。
就是以类似于 p2(1) = [ p2(0) + p1(0) ] /2 的方式进行。
个人认为,既然 d() 本身也只是起上界的作用,不是求出精确的误差,那我们也可以在此基础上取一个更高的上界。
因为:
d( p1, p0p3 ) <= d( p1, p') (p'是 p0p3 的中点)
用这个来代替 点到直线的距离 ,程序会更简单。
再进一步,假设给定的误差 是距离的平方,即判断条件改为 d(p1, p')^2 < E,求距离 d() 的时候就不必开方了~~~~
不过,过分将上界扩大,可能在曲线实际上已经达到要求,但我们的判断条件还没有达到要求时,继续进行运算,这可能反而增加了时间。具体效果如何应该值得斟酌……
tuxw
2003-12-17
打赏
举报
回复
TO : dengsf(杀神大菜鸟)
上面的这算法最大的好处是去掉了浮点乘法运行算(但求点到直线的距离还是要的),估计速度会快很多,递归的浓度可以由设定的误差值控制,但下面的递推公式有点不理解,能否进一步解释一下,谢谢:
Pi(j) = [pi(j-1) + pi-1(j-1)] / 2
另外,上面的公式是不是有笔误?会不会是这样的:
Pi(j) = [Pi(j-1) + P(i-1)(j-1)] / 2
LeeMaRS
2003-12-16
打赏
举报
回复
M$的算法都是层层优化的。。。岂非我们可以相比
tuxw
2003-12-16
打赏
举报
回复
谢谢大家,下面是我按上面方法写的函数,是可以生成曲线的,我用它来画字体的轮廓,
但是速度很慢, 跟VC提供的 PolyBezierTo 简直不可同日而语, 请问下面的代码应该怎么优化一下,或有什么更简单的算法?
还有一点就是 t 循环的步长不好确定,太细则花的时间更多,且精度无法与 VC 提供的
PolyBezierTo 相比,放大后可看出明显的区别, 很想知道 VC 是怎么做到的
void myPolyBezierTo( CDC *pDC, const POINT* lpPoints )
{
// 根据起点到终点的x、y变量化量,最较大的一个用来确定步长
// 曲线不能往回绕,以保证起起到终点能表最大变化量
int dx = lpPoints[0].x - lpPoints[3].x;
if( dx < 0 ) dx *= -1;
int dy = lpPoints[0].y - lpPoints[3].y;
if( dy < 0 ) dy *= -1;
dy = dx > dy ? dx : dy;
double step = 1.0 / dy; // 步长
pDC->MoveTo( lpPoints[0] );
double x, y, t3, t2, t11, t12, t13;
for( double t = 0.0; t <= 1.0; t += step ) {
t11 = 1-t;
t12 = t11 * t11;
t13 = t12 * t11; // (1-t)^3
t2 = t * t;
t3 = t2 * t; // t^3
// x = (1-t)^3 *x0 + 3*t*(1-t)^2 *x1 + 3*t^2*(1-t) *x2 + t^3 *x3
// y = (1-t)^3 *y0 + 3*t*(1-t)^2 *y1 + 3*t^2*(1-t) *y2 + t^3 *y3
// 0 <= t <= 1
x = t13 * lpPoints[0].x
+ 3 * t * t12 * lpPoints[1].x
+ 3 * t2 * t11 * lpPoints[2].x
+ t3 * lpPoints[3].x;
y = t13 * lpPoints[0].y
+ 3 * t * t12 * lpPoints[1].y
+ 3 * t2 * t11 * lpPoints[2].y
+ t3 * lpPoints[3].y;
pDC->LineTo( x, y );
}
}
saint001
2003-12-16
打赏
举报
回复
给定p0,p1,p2,p3
p00=p0
p01=p1
p02=p2
p03=p3
p10=p00*(1-t)+p01*t
p11=p01*(1-t)+p02*t
p12=p02*(1-t)+p03*t
p20=p10*(1-t)+p11*t
p21=p11*(1-t)+p12*t
p30=p20*(1-t)+p21*t
p(t)=p30, 0<=t<=1
saint001
2003-12-16
打赏
举报
回复
德卡斯特里奥算法应用到n=3的时候
四个控制顶点p0,p1,p2,p3
p00=p0,p10=p1,p20=p2,p30=p3
p01=p00*(1-t)+p10*t
p11=p10*(1-t)+p20*t
p21=p20*(1-t)*p30*t
p02=p01*(1-t)+p11*t
p12=p11*(1-t)+p21*t
p03=p02*(1-t)+p12*t
p(t)=p03 0<=t<=1
优点:线性运算,并且中间过程生成的变量有几何意义,便于分析几何特征
加载更多回复(2)
曲线
生成
算法
的C#gdi+实现(
贝塞尔曲线
、样条曲线、B样条曲线)
曲线
生成
算法
的C#gdi+实现(
贝塞尔曲线
、样条曲线、B样条曲线)
贝塞尔曲线
函数 曲线平滑
算法
详细源码讲解如何计算
贝塞尔曲线
,可以实现曲线的平滑
算法
原生3次
贝塞尔曲线
算法
地图
1.点在多边形
算法
2.散点
生成
光滑的3次
贝塞尔曲线
算法
(多段闭合) (知乎里一个帖子找到的一个国外网站下的) 3.中国个省市json数据 4.247个国家的基本地理数据
VC++三次样条插值&
贝塞尔曲线
&GDI+平滑曲线程序
程序为MFC对话框程序 包括 三次样条插值的计算类,自己封的很好用。
贝塞尔曲线
GDI+平滑曲线 用这三种
算法
来平滑曲线,在程序中可以很值观的看出来,自己写的,给点辛苦钱吧。
沿着
贝塞尔曲线
移动的按钮
请
参考:Android动画学习Demo(3) 沿着
贝塞尔曲线
移动的Property Animation
数据结构与算法
33,007
社区成员
35,326
社区内容
发帖
与我相关
我的任务
数据结构与算法
数据结构与算法相关内容讨论专区
复制链接
扫一扫
分享
社区描述
数据结构与算法相关内容讨论专区
社区管理员
加入社区
获取链接或二维码
近7日
近30日
至今
加载中
查看更多榜单
社区公告
暂无公告
试试用AI创作助手写篇文章吧
+ 用AI写文章