赞
踩
首先我们应该来了解一下为什么要有B样条曲线,主要原因是因为Bezier曲线的不足:
为了保留Bezier方法的优点,仍采用控制顶点定义曲线。改用B样条基函数代替伯恩斯坦基函数。舍恩伯格指出,B样条具有局部支撑性质。B样条基函数是多项式样条空间具有最小支撑的一组基函数,故也被称之为基本样条(Basis Spline),简称B样条(B-Spline)。
B样条的递推定义,常用de Boor-Cox递推公式如下:
具体的参数解释如下:
从上式中我们可以看出其通式符合递归条件,如下图基函数生成过程的三角阵列中所示;
举例如下:
当k=0时,由式可以直接给出零次B样条。
具体图形表示如下:
可以看出,零次B样条基函数Fi,o(t)在其定义区间上形状为一水平直线段。只在一个区间[ti,ti+i]上不为零,在其他子区间均为零。Fi,o(t)称为平台函数。
由Fi,o(t)移位,得到:
且根据公式:
结合以上两式,得:
图形表达上式如下:
二次B样条表达如下:
其计算过程:
其计算过程:
图形表达上式如下:
三次B样条表达如下
其计算过程:
带入,得:
图形表达上式如下:
//根据参数t值和次数k与节点矢量knot,计算第i个k次的B样条基函数 double BasisFunctionValue(double t, int i, int k) { double value1,value2,value; if(k==0) { if(t>=knot[i] && t<knot[i+1]) return 1.0; else return 0.0; } if (k>0) { if(t<knot[i]||t>knot[i+k+1]) return 0.0; else { double coffcient1,coffcient2;//凸组合系数1,凸组合系数2 double denominator=0.0;//分母 denominator=knot[i+k]-knot[i];//递推公式第一项分母 if(denominator==0.0)//约定0/0 coffcient1=0.0; else coffcient1=(t-knot[i])/denominator; denominator=knot[i+k+1]-knot[i+1];//递推公式第二项分母 if(0.0==denominator)//约定0/0 coffcient2=0.0; else coffcient2=(knot[i+k+1]-t)/denominator; value1=coffcient1*BasisFunctionValue(t,i,k-1);//递推公式第一项的值 value2=coffcient2*BasisFunctionValue(t,i+1,k-1);//递推公式第二项的值 value=value1+value2;//基函数的值 } } return value; }
B样条曲线的数学定义为:
具体详释:
尤为注意的是:
B样条有三大要素:节点,控制点,阶次。
其中B样条曲线所对应的节点向量区间:u[uk-1,un+1]
其具体定义理解可参照B站教程:计算机图形学bezier曲线曲面B样条曲线曲面
根据节点向量中节点的分布可以将B样条曲线分为以下四类:均匀B样条曲线、准均匀B样条曲线、分段Bezier曲线和非均匀B样条曲线。
二次均匀B样条曲线的几何意义,如下图:
CP2 P[6];//控制点 double knot[9];//节点数组 int n;//控制点数-1 int k;//次数 CGeometricfiguretestView::CGeometricfiguretestView() { // TODO: add construction code here knot[0]=-0.5,knot[1]=-0.25,knot[2]=0.0,knot[3]=0.25,knot[4]=0.5,knot[5]=0.75;//均匀二次B样条 knot[6]= 1.0,knot[7]= 1.25,knot[8]= 1.5; n=5,k=2; P[0].x=-460, P[0].y=-49; P[1].x=-355, P[1].y=204; P[2].x= -63, P[2].y=241; P[3].x= 66, P[3].y=-117; P[4].x= 264, P[4].y=-101; P[5].x= 400, P[5].y=208; } void CGeometricfiguretestView::DrawBSplineCurve(CDC* pDC)//绘制B样条曲线 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,2,RGB(255,0,0));//曲线颜色 pOldPen=pDC->SelectObject(&NewPen); double tStep=0.05; for(double t=0.0;t<=1.0;t+=tStep) { CP2 p(0,0); for(int i=0;i<=n;i++) { double BValue=BasisFunctionValue(t,i,k); p+=P[i]*BValue;//B样条曲线定义 } if(0.0==t) pDC->MoveTo(ROUND(p.x),ROUND(p.y)); else pDC->LineTo(ROUND(p.x),ROUND(p.y)); } pDC->SelectObject(pOldPen); NewPen.DeleteObject(); } void CGeometricfiguretestView::DrawControlPolygon(CDC* pDC)//绘制控制多边形 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,3,RGB(0,0,0)); pOldPen=pDC->SelectObject(&NewPen); CBrush NewBrush,*pOldBrush; NewBrush.CreateSolidBrush(RGB(0,0,0)); pOldBrush=pDC->SelectObject(&NewBrush); for(int i=0;i<=n;i++) { if(0==i) { pDC->MoveTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } else { pDC->LineTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } } pDC->SelectObject(pOldBrush); pDC->SelectObject(pOldPen); } double CGeometricfiguretestView::BasisFunctionValue(double t, int i, int k)//根据参数t值和次数k与节点矢量knot,计算第i个k次的B样条基函数 { double value1,value2,value; if(k==0) { if(t>=knot[i] && t<knot[i+1]) return 1.0; else return 0.0; } if (k>0) { if(t<knot[i]||t>knot[i+k+1]) return 0.0; else { double coffcient1,coffcient2;//凸组合系数1,凸组合系数2 double denominator=0.0;//分母 denominator=knot[i+k]-knot[i];//递推公式第一项分母 if(denominator==0.0)//约定0/0 coffcient1=0.0; else coffcient1=(t-knot[i])/denominator; denominator=knot[i+k+1]-knot[i+1];//递推公式第二项分母 if(0.0==denominator)//约定0/0 coffcient2=0.0; else coffcient2=(knot[i+k+1]-t)/denominator; value1=coffcient1*BasisFunctionValue(t,i,k-1);//递推公式第一项的值 value2=coffcient2*BasisFunctionValue(t,i+1,k-1);//递推公式第二项的值 value=value1+value2;//基函数的值 } } return value; } void CGeometricfiguretestView::OnDraw(CDC* pDC) { CTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: add draw code for native data here CRect rect;//定义矩形 GetClientRect(&rect);//获得客户区的大小 pDC->SetMapMode(MM_ANISOTROPIC);//pDC自定义坐标系 pDC->SetWindowExt(rect.Width(),rect.Height());//设置窗口范围 pDC->SetViewportExt(rect.Width(),-rect.Height());//设置视区范围,x轴水平向右,y轴垂直向上 pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2);//客户区中心为原点 DrawBSplineCurve(pDC); DrawControlPolygon(pDC); }
三次均匀B样条曲线的几何意义,如下图:
CP2 P[6];//控制点 double knot[11];//节点数组 int n;//控制点数-1 int k;//次数 CGeometricfiguretestView::CGeometricfiguretestView() { // TODO: add construction code here knot[0]=-3/3.0,knot[1]=-2/3.0,knot[2]=-1/3.0,knot[3]=0.0,knot[4]=1/3.0,knot[5]=2/3.0;//均匀三次B样条 knot[6]=1.0,knot[7]= 4/3.0,knot[8]= 5/3.0,knot[9]=6/3.0; n=5,k=3; P[0].x=-460, P[0].y=-49; P[1].x=-355, P[1].y=204; P[2].x= -63, P[2].y=241; P[3].x= 66, P[3].y=-117; P[4].x= 264, P[4].y=-101; P[5].x= 400, P[5].y=208; } void CGeometricfiguretestView::OnDraw(CDC* pDC) { CTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: add draw code for native data here CRect rect;//定义矩形 GetClientRect(&rect);//获得客户区的大小 pDC->SetMapMode(MM_ANISOTROPIC);//pDC自定义坐标系 pDC->SetWindowExt(rect.Width(),rect.Height());//设置窗口范围 pDC->SetViewportExt(rect.Width(),-rect.Height());//设置视区范围,x轴水平向右,y轴垂直向上 pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2);//客户区中心为原点 DrawBSplineCurve(pDC); DrawControlPolygon(pDC); } void CGeometricfiguretestView::DrawBSplineCurve(CDC* pDC)//绘制B样条曲线 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,2,RGB(255,0,0));//曲线颜色 pOldPen=pDC->SelectObject(&NewPen); double tStep=0.01; for(double t=0.0;t<=1.0;t+=tStep) { CP2 p(0.0,0.0); for(int i=0;i<=n;i++) { double BValue=BasisFunctionValue(t,i,k); p+=P[i]*BValue;//B样条曲线定义 } if(0.0==t) pDC->MoveTo(ROUND(p.x),ROUND(p.y)); else pDC->LineTo(ROUND(p.x),ROUND(p.y)); } pDC->SelectObject(pOldPen); NewPen.DeleteObject(); } void CGeometricfiguretestView::DrawControlPolygon(CDC* pDC)//绘制控制多边形 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,3,RGB(0,0,0)); pOldPen=pDC->SelectObject(&NewPen); CBrush NewBrush,*pOldBrush; NewBrush.CreateSolidBrush(RGB(0,0,0)); pOldBrush=pDC->SelectObject(&NewBrush); for(int i=0;i<=n;i++) { if(0==i) { pDC->MoveTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } else { pDC->LineTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } } pDC->SelectObject(pOldBrush); pDC->SelectObject(pOldPen); } double CGeometricfiguretestView::BasisFunctionValue(double t, int i, int k)//根据参数t值和次数k与节点矢量knot,计算第i个k次的B样条基函数 { double value1,value2,value; if(k==0) { if(t>=knot[i] && t<knot[i+1]) return 1.0; else return 0.0; } if (k>0) { if(t<knot[i]||t>knot[i+k+1]) return 0.0; else { double coffcient1,coffcient2;//凸组合系数1,凸组合系数2 double denominator=0.0;//分母 denominator=knot[i+k]-knot[i];//递推公式第一项分母 if(denominator==0.0)//约定0/0 coffcient1=0.0; else coffcient1=(t-knot[i])/denominator; denominator=knot[i+k+1]-knot[i+1];//递推公式第二项分母 if(0.0==denominator)//约定0/0 coffcient2=0.0; else coffcient2=(knot[i+k+1]-t)/denominator; value1=coffcient1*BasisFunctionValue(t,i,k-1);//递推公式第一项的值 value2=coffcient2*BasisFunctionValue(t,i+1,k-1);//递推公式第二项的值 value=value1+value2;//基函数的值 } } return value; }
1.二重顶点
在曲线设计中,若要使B样条曲线与控制多边形的边相切,可使用二重点方法,如图所示。
2.三重顶点
要想在曲线中出现尖点,可使用三重点方法,如图所示。
3.三顶点共线
当三个顶点共线时,△P1P2P3退化为一段直线。可用于处理两段弧的相接。
4.四顶点共线
当四个顶点共线时,控制多边形P1P2P3 P4退化为一段直线,相应的B样条曲线段也退化为一段直线,可用于处理两段曲线之间接入一段直线的问题。
当控制多边形的首末端点全部为三重点,这可以使三次均匀B样条曲线过控制多边形的首末端点。
均匀B样条的特点是节点等距分布,由各节点形成的B样条相同,可视为同一B样条的简单平移。均匀B样条基函数在[0, 1]区间上具有统一的表达式,使得计算与处理简单方便。一般情况下,应用均匀B样条可以获得满意的效果,而且计算效率高。但均匀B样条曲线有一个缺点,即未保留Bezier曲线的端点几何性质。曲线的首末端点不再是控制多边形的首末顶点。准均匀B样条曲线就是为了解决这个问题而提出的,目的是对曲线在端点的行为进行控制。
k次准均匀B样条曲线的节点矢量中,两端节点具有重复度k + 1,所有内节点呈均匀分布。因此,准均匀B样条曲线具有同次Bezier曲线的端点几何性质。两端节点k + 1重复度的出现,导致准均匀B样条曲线在计算与处理上,要比均匀B样条曲线复杂得多。实践中,是选择均匀B样条曲线还是选择准均匀B样条曲线,是需要斟酌的。
CGeometricfiguretestView::CGeometricfiguretestView() { // TODO: add construction code here knot[0]=0,knot[1]=0,knot[2]=0,knot[3]=1/4.0,knot[4]=2/4.0,knot[5]=3/4.0;//准均匀二次均匀B样条 knot[6]=1,knot[7]=1,knot[8]=1; n=5,k=2; P[0].x=-460, P[0].y=-49; P[1].x=-355, P[1].y=204; P[2].x= -63, P[2].y=241; P[3].x= 66, P[3].y=-117; P[4].x= 264, P[4].y=-101; P[5].x= 400, P[5].y=208; } void CGeometricfiguretestView::OnDraw(CDC* pDC) { CTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: add draw code for native data here CRect rect;//定义矩形 GetClientRect(&rect);//获得客户区的大小 pDC->SetMapMode(MM_ANISOTROPIC);//pDC自定义坐标系 pDC->SetWindowExt(rect.Width(),rect.Height());//设置窗口范围 pDC->SetViewportExt(rect.Width(),-rect.Height());//设置视区范围,x轴水平向右,y轴垂直向上 pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2);//客户区中心为原点 DrawBSplineCurve(pDC); DrawControlPolygon(pDC); } void CGeometricfiguretestView::DrawBSplineCurve(CDC* pDC)//绘制B样条曲线 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,2,RGB(255,0,0));//曲线颜色 pOldPen=pDC->SelectObject(&NewPen); double tStep=0.01; for(double t=0.0;t<=1.0;t+=tStep) { CP2 p(0.0,0.0); for(int i=0;i<=n;i++) { double BValue=BasisFunctionValue(t,i,k); p+=P[i]*BValue;//B样条曲线定义 } if(0.0==t) pDC->MoveTo(ROUND(p.x),ROUND(p.y)); else pDC->LineTo(ROUND(p.x),ROUND(p.y)); } pDC->SelectObject(pOldPen); NewPen.DeleteObject(); } void CGeometricfiguretestView::DrawControlPolygon(CDC* pDC)//绘制控制多边形 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,3,RGB(0,0,0)); pOldPen=pDC->SelectObject(&NewPen); CBrush NewBrush,*pOldBrush; NewBrush.CreateSolidBrush(RGB(0,0,0)); pOldBrush=pDC->SelectObject(&NewBrush); for(int i=0;i<=n;i++) { if(0==i) { pDC->MoveTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } else { pDC->LineTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } } pDC->SelectObject(pOldBrush); pDC->SelectObject(pOldPen); } double CGeometricfiguretestView::BasisFunctionValue(double t, int i, int k)//根据参数t值和次数k与节点矢量knot,计算第i个k次的B样条基函数 { double value1,value2,value; if(k==0) { if(t>=knot[i] && t<knot[i+1]) return 1.0; else return 0.0; } if (k>0) { if(t<knot[i]||t>knot[i+k+1]) return 0.0; else { double coffcient1,coffcient2;//凸组合系数1,凸组合系数2 double denominator=0.0;//分母 denominator=knot[i+k]-knot[i];//递推公式第一项分母 if(denominator==0.0)//约定0/0 coffcient1=0.0; else coffcient1=(t-knot[i])/denominator; denominator=knot[i+k+1]-knot[i+1];//递推公式第二项分母 if(0.0==denominator)//约定0/0 coffcient2=0.0; else coffcient2=(knot[i+k+1]-t)/denominator; value1=coffcient1*BasisFunctionValue(t,i,k-1);//递推公式第一项的值 value2=coffcient2*BasisFunctionValue(t,i+1,k-1);//递推公式第二项的值 value=value1+value2;//基函数的值 } } return value; }
CGeometricfiguretestView::CGeometricfiguretestView() { // TODO: add construction code here knot[0]=0,knot[1]=0,knot[2]=0,knot[3]=0,knot[4]=1/3.0,knot[5]=2/3.0;//准均匀三次均匀B样条 knot[6]=1,knot[7]=1,knot[8]=1,knot[9]=1; n=5,k=3; P[0].x=-460, P[0].y=-49; P[1].x=-355, P[1].y=204; P[2].x= -63, P[2].y=241; P[3].x= 66, P[3].y=-117; P[4].x= 264, P[4].y=-101; P[5].x= 400, P[5].y=208; } void CGeometricfiguretestView::OnDraw(CDC* pDC) { CTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: add draw code for native data here CRect rect;//定义矩形 GetClientRect(&rect);//获得客户区的大小 pDC->SetMapMode(MM_ANISOTROPIC);//pDC自定义坐标系 pDC->SetWindowExt(rect.Width(),rect.Height());//设置窗口范围 pDC->SetViewportExt(rect.Width(),-rect.Height());//设置视区范围,x轴水平向右,y轴垂直向上 pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2);//客户区中心为原点 DrawBSplineCurve(pDC); DrawControlPolygon(pDC); } void CGeometricfiguretestView::DrawBSplineCurve(CDC* pDC)//绘制B样条曲线 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,2,RGB(255,0,0));//曲线颜色 pOldPen=pDC->SelectObject(&NewPen); double tStep=0.01; for(double t=0.0;t<=1.0;t+=tStep) { CP2 p(0.0,0.0); for(int i=0;i<=n;i++) { double BValue=BasisFunctionValue(t,i,k); p+=P[i]*BValue;//B样条曲线定义 } if(0.0==t) pDC->MoveTo(ROUND(p.x),ROUND(p.y)); else pDC->LineTo(ROUND(p.x),ROUND(p.y)); } pDC->SelectObject(pOldPen); NewPen.DeleteObject(); } void CGeometricfiguretestView::DrawControlPolygon(CDC* pDC)//绘制控制多边形 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,3,RGB(0,0,0)); pOldPen=pDC->SelectObject(&NewPen); CBrush NewBrush,*pOldBrush; NewBrush.CreateSolidBrush(RGB(0,0,0)); pOldBrush=pDC->SelectObject(&NewBrush); for(int i=0;i<=n;i++) { if(0==i) { pDC->MoveTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } else { pDC->LineTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } } pDC->SelectObject(pOldBrush); pDC->SelectObject(pOldPen); } double CGeometricfiguretestView::BasisFunctionValue(double t, int i, int k)//根据参数t值和次数k与节点矢量knot,计算第i个k次的B样条基函数 { double value1,value2,value; if(k==0) { if(t>=knot[i] && t<knot[i+1]) return 1.0; else return 0.0; } if (k>0) { if(t<knot[i]||t>knot[i+k+1]) return 0.0; else { double coffcient1,coffcient2;//凸组合系数1,凸组合系数2 double denominator=0.0;//分母 denominator=knot[i+k]-knot[i];//递推公式第一项分母 if(denominator==0.0)//约定0/0 coffcient1=0.0; else coffcient1=(t-knot[i])/denominator; denominator=knot[i+k+1]-knot[i+1];//递推公式第二项分母 if(0.0==denominator)//约定0/0 coffcient2=0.0; else coffcient2=(knot[i+k+1]-t)/denominator; value1=coffcient1*BasisFunctionValue(t,i,k-1);//递推公式第一项的值 value2=coffcient2*BasisFunctionValue(t,i+1,k-1);//递推公式第二项的值 value=value1+value2;//基函数的值 } } return value; }
分段Bezier是B样条曲线的一种特殊类型,是一组顺序首尾相接且同为k次的Bezier曲线。在STEP中是这样来表示的:节点矢量中首末端节点重复度取成k + 1,所有内节点取成重复度k。选用分段Bezier曲线有个限制条件:控制顶点数减1必须是次数的整数倍,即n/k = 正整数;否则不能生成曲线。即使满足整数倍要求,所生成的分段Bezier曲线在连接点处也只能达到C1连续性。分段Bezier的主要用途之一,就是把几何连续的分段多项式曲线统一采用B样条表示。
CGeometricfiguretestView::CGeometricfiguretestView() { // TODO: add construction code here knot[0]=0.0,knot[1]=0.0,knot[2]=0.0,knot[3]=0.0,knot[4]=0.5,knot[5]=0.5;//分段三次Bezier knot[6]=0.5,knot[7]=1.0,knot[8]=1.0,knot[9]=1.0,knot[10]=1.0; n=6,k=3; P[0].x=-319, P[0].y=-14; P[1].x=-269, P[1].y=202; P[2].x= -61, P[2].y=198; P[3].x= -32, P[3].y=13; P[4].x= 54, P[4].y=-116; P[5].x= 253, P[5].y=-76; P[6].x= 296, P[6].y=66; } void CGeometricfiguretestView::OnDraw(CDC* pDC) { CTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: add draw code for native data here CRect rect;//定义矩形 GetClientRect(&rect);//获得客户区的大小 pDC->SetMapMode(MM_ANISOTROPIC);//pDC自定义坐标系 pDC->SetWindowExt(rect.Width(),rect.Height());//设置窗口范围 pDC->SetViewportExt(rect.Width(),-rect.Height());//设置视区范围,x轴水平向右,y轴垂直向上 pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2);//客户区中心为原点 DrawBSplineCurve(pDC); DrawControlPolygon(pDC); } void CGeometricfiguretestView::DrawBSplineCurve(CDC* pDC)//绘制B样条曲线 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,2,RGB(255,0,0));//曲线颜色 pOldPen=pDC->SelectObject(&NewPen); double tStep=0.01; for(double t=0.0;t<=1.0;t+=tStep) { CP2 p(0.0,0.0); for(int i=0;i<=n;i++) { double BValue=BasisFunctionValue(t,i,k); p+=P[i]*BValue;//B样条曲线定义 } if(0.0==t) pDC->MoveTo(ROUND(p.x),ROUND(p.y)); else pDC->LineTo(ROUND(p.x),ROUND(p.y)); } pDC->SelectObject(pOldPen); NewPen.DeleteObject(); } void CGeometricfiguretestView::DrawControlPolygon(CDC* pDC)//绘制控制多边形 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,3,RGB(0,0,0)); pOldPen=pDC->SelectObject(&NewPen); CBrush NewBrush,*pOldBrush; NewBrush.CreateSolidBrush(RGB(0,0,0)); pOldBrush=pDC->SelectObject(&NewBrush); for(int i=0;i<=n;i++) { if(0==i) { pDC->MoveTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } else { pDC->LineTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } } pDC->SelectObject(pOldBrush); pDC->SelectObject(pOldPen); } double CGeometricfiguretestView::BasisFunctionValue(double t, int i, int k)//根据参数t值和次数k与节点矢量knot,计算第i个k次的B样条基函数 { double value1,value2,value; if(k==0) { if(t>=knot[i] && t<knot[i+1]) return 1.0; else return 0.0; } if (k>0) { if(t<knot[i]||t>knot[i+k+1]) return 0.0; else { double coffcient1,coffcient2;//凸组合系数1,凸组合系数2 double denominator=0.0;//分母 denominator=knot[i+k]-knot[i];//递推公式第一项分母 if(denominator==0.0)//约定0/0 coffcient1=0.0; else coffcient1=(t-knot[i])/denominator; denominator=knot[i+k+1]-knot[i+1];//递推公式第二项分母 if(0.0==denominator)//约定0/0 coffcient2=0.0; else coffcient2=(knot[i+k+1]-t)/denominator; value1=coffcient1*BasisFunctionValue(t,i,k-1);//递推公式第一项的值 value2=coffcient2*BasisFunctionValue(t,i+1,k-1);//递推公式第二项的值 value=value1+value2;//基函数的值 } } return value; }
与其他类型的B样条曲线不同的是,给定控制点Pi, i = 0, 1,…, n,欲定义一条k次非均匀B样条曲线,还必须计算节点矢量 中具体的节点值。利用曲线的分段连接点与控制多边形的边对应关系,可以自动计算节点矢量。常用的算法有Hartley-Judd算法。
1978年,Hartley和Judd认为应该根据曲线的连续性,分别考察每个控制多边形的k条边的和,然后再予以规范化。这就是Hartley-Judd算法。定义域内节点区间长度按下式计算:
CGeometricfiguretestView::CGeometricfiguretestView() { // TODO: add construction code here n=4; k=2; P[0].x=-200, P[0].y=100; P[1].x=-150, P[1].y=-100; P[2].x=0, P[2].y=250; P[3].x=150, P[3].y=-100; P[4].x=200, P[4].y=100; } void CGeometricfiguretestView::OnDraw(CDC* pDC) { CTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: add draw code for native data here CRect rect;//定义矩形 GetClientRect(&rect);//获得客户区的大小 pDC->SetMapMode(MM_ANISOTROPIC);//pDC自定义坐标系 pDC->SetWindowExt(rect.Width(),rect.Height());//设置窗口范围 pDC->SetViewportExt(rect.Width(),-rect.Height());//设置视区范围,x轴水平向右,y轴垂直向上 pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2);//客户区中心为原点 GetKnotVector(); DrawBSplineCurve(pDC); DrawControlPolygon(pDC); } void CGeometricfiguretestView::GetKnotVector()// Hartley-Judd方法获取节点值 { for(int i=0;i<=k;i++) //小于次数k的节点值为0 knot[i]=0.0; for(int i=n+1;i<=n+k+1;i++)//大于n的节点值为1 knot[i]=1.0; //计算n-k个内节点 for(int i=k+1;i<=n;i++) { double sum=0.0; for(int j=k+1;j<=i;j++)//公式(5-24) { double numerator=0.0;//计算分子 for(int loop=j-k;loop<=j-1;loop++) { numerator+=sqrt((P[loop].x-P[loop-1].x)*(P[loop].x-P[loop-1].x)+(P[loop].y-P[loop-1].y)*(P[loop].y-P[loop-1].y));//计算两个点之间的距离 } double denominator=0.0;//计算分母 for(int loop1=k+1;loop1<=n+1;loop1++) { for(int loop2=loop1-k;loop2<=loop1-1;loop2++) { denominator+=sqrt((P[loop2].x-P[loop2-1].x)*(P[loop2].x-P[loop2-1].x)+(P[loop2].y-P[loop2-1].y)*(P[loop2].y-P[loop2-1].y));//计算两个点之间的距离 } } sum+=numerator/denominator;//计算节点值 } knot[i]=sum; } } void CGeometricfiguretestView::DrawBSplineCurve(CDC* pDC)//绘制B样条曲线 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,2,RGB(255,0,0));//曲线颜色 pOldPen=pDC->SelectObject(&NewPen); double tStep=0.01; for(double t=0.0;t<=1.0;t+=tStep) { CP2 p(0.0,0.0); for(int i=0;i<=n;i++) { double BValue=BasisFunctionValue(t,i,k); p+=P[i]*BValue;//B样条曲线定义 } if(0.0==t) pDC->MoveTo(ROUND(p.x),ROUND(p.y)); else pDC->LineTo(ROUND(p.x),ROUND(p.y)); } pDC->SelectObject(pOldPen); NewPen.DeleteObject(); } void CGeometricfiguretestView::DrawControlPolygon(CDC* pDC)//绘制控制多边形 { CPen NewPen,*pOldPen; NewPen.CreatePen(PS_SOLID,3,RGB(0,0,0)); pOldPen=pDC->SelectObject(&NewPen); CBrush NewBrush,*pOldBrush; NewBrush.CreateSolidBrush(RGB(0,0,0)); pOldBrush=pDC->SelectObject(&NewBrush); for(int i=0;i<=n;i++) { if(0==i) { pDC->MoveTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } else { pDC->LineTo(ROUND(P[i].x),ROUND(P[i].y)); pDC->Ellipse(ROUND(P[i].x)-5,ROUND(P[i].y)-5,ROUND(P[i].x)+5,ROUND(P[i].y)+5); } } pDC->SelectObject(pOldBrush); pDC->SelectObject(pOldPen); } double CGeometricfiguretestView::BasisFunctionValue(double t, int i, int k)//根据参数t值和次数k与节点矢量knot,计算第i个k次的B样条基函数 { double value1,value2,value; if(k==0) { if(t>=knot[i] && t<knot[i+1]) return 1.0; else return 0.0; } if(k>0) { if(t<knot[i]||t>knot[i+k+1]) return 0.0; else { double coffcient1,coffcient2;//凸组合系数1,凸组合系数2 double denominator=0.0;//分母 denominator=knot[i+k]-knot[i];//递推公式第一项分母 if(denominator==0.0)//约定0/0 coffcient1=0.0; else coffcient1=(t-knot[i])/denominator; denominator=knot[i+k+1]-knot[i+1];//递推公式第二项分母 if(0.0==denominator)//约定0/0 coffcient2=0.0; else coffcient2=(knot[i+k+1]-t)/denominator; value1=coffcient1*BasisFunctionValue(t,i,k-1);//递推公式第一项的值 value2=coffcient2*BasisFunctionValue(t,i+1,k-1);//递推公式第二项的值 value=value1+value2;//基函数的值 } } return value; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。