首先我们应该来了解一下为什么要有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)移位,得到:
且根据公式:
结合以上两式,得:
图形表达上式如下:
//根据参数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样条曲线所对应的节点向量区间:u[uk-1,un+1]
其具体定义理解可参照B站教程:计算机图形学bezier曲线曲面B样条曲线曲面
根据节点向量中节点的分布可以将B样条曲线分为以下四类:均匀B样条曲线、准均匀B样条曲线、分段Bezier曲线和非均匀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);
}
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;
}