B样条方法在表示与设计自由曲线曲面时展现出强大的威力,但在设计与表示初等曲线曲面时却遇到了麻烦。因为B样条曲线、及其特例的Bezier曲线都不能精确表示除抛物线以外的二次曲线弧,B样条曲面、及其特例的Bezier曲面都不能精确表示除抛物面以外的二次曲面,而只能给出近似表示。解决这一问题的途径是改进现有的B样条方法,在保留描述自由曲线曲面长处的同时,扩充其表示二次曲线弧与二次曲面的能力,这种方法就是有理B样条方法。
由于在曲线曲面描述中,B样条方法更多地以非均匀类型出现,而均匀、准均匀、分段Bezier三种类型又被看成是非均匀类型的特例,所以习惯上称之为非均匀有理B样条(Non-Uniform Rational B-Splines)方法,简称为NURBS方法。NURBS方法提出的主要理由是,寻找与描述自由曲线曲面的B样条方法相统一的,而又能精确表示二次曲线曲面的数学方法。非均匀B样条采用分段参数整数多项式,而NURBS方法采用分子分母分别是分段参数多项式函数与分段多项式的分式表示,是有理的。与有理Bezier方法一样,NURBS方法引入了权因子和分母。NURBS方法是在有理Bezier方法与非有理B样条方法的基础上发展起来的。
NURBS方法在CAD/CAM以及计算机图形学领域获得越来越广泛的应用,优点主要表现在以下几个方面:
- 将初等曲线曲面与自由曲线曲面的表达方式统一起来。
- 增加了权因子,有利于对曲线曲面形状的控制和修改。
- 非有理B样条、有理与非有理Bezier方法是NURBS的特例。
- 在比例、旋转、平移、错切以及平行和透视投影变换下是不变的。
一、NURBS曲线的定义及几何性质
1.1 有理分式表示
通常在实际应用中,端节点值分别取a = 0, b = 1。
1.2 有理基函数表示
则有,
式子中,Ri,k(t) ,i=0,1,…,n称为k次有理基函数。
1.3 齐次坐标表示
1.4 几何性质
如下图,绘制的是3段三次NURBS曲线(n=5, k=3),虚线表示中间一段曲线。这段曲线包含在P1, P2, P3, P4构成的凸包内。图中这个凸包也用虚线围成。
二、权因子对NURBS曲线形状的影响
由NURBS曲线的定义可知,改变权因子、移动控制点或改变节点矢量,都将使NURBS曲线的形状发生变化。实践证明,采用改变节点矢量的方法修改NURBS曲线缺乏直观的几何意义,难以预料修改的结果。因此实际应用中,往往通过调整权因子或移动控制点来修改曲线形状。
在实际应用中,当需要修改曲线形状时,往往首先移动控制顶点。在曲线形状大致确定后,再根据需要在小范围内调整权因子,使曲线从整体到局部逐步达到要求。
三、NURBS曲线的形状修改
像非有理B样条曲线那样, NURBS曲线也可按所取节点向量划分成四种类型。
非有理B样条曲线的DeBoor算法求曲线上的点、插入节点、升阶等都可以推广到NURBS曲线。这些算法都应对高一维的带权控制顶点执行,然后,取其在0-1超平面上的投影即为所求。
采用NURBS方法可以将顺序相连的各种连续性的各种非有理与有理Bezier曲线、非有理和有理B样条曲线在更高层次上组合。
采用类似非有理B样条的组合方法,组合在一起,用一个统一方程表示,成为最具一般意义的NURBS曲线。
3.1 重新定位控制定点
3.2 反插节点
3.3 重新确定权因子
3.4 修改界定曲线部分
四、NURBS曲线代码实现
4.1 控制点初始化
CP2 P[7];//控制点double W[7];//权因子double knot[11];//节点数组int n;//控制点数-1int k;//次数
CGeometricfiguretestView::CGeometricfiguretestView()
{// TODO: add construction code heren=6,k=3;P[0].x=-280; P[0].y=30;//控制点P[1].x=-250; P[1].y=180;P[2].x=0; P[2].y=200;P[3].x=-100; P[3].y=-100;P[4].x=150; P[4].y=-100;P[5].x=130; P[5].y=120;P[6].x=230; P[6].y=150;W[0]=1.0,W[1]=2.0,W[2]=2.0,W[3]=2.0,W[4]=2.0,W[5]=2.0,W[6]=1.0;//权因子
}
4.2 绘制
void CGeometricfiguretestView::OnDraw(CDC* pDC)
{CTestDoc* pDoc = GetDocument();ASSERT_VALID(pDoc);if (!pDoc)return;// TODO: add draw code for native data hereCRect 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();DrawNurbsCurve(pDC);DrawControlPolygon(pDC);
}void CGeometricfiguretestView::DrawNurbsCurve(CDC* pDC)//绘制NURBS曲线
{CPen NewPen,*pOldPen;NewPen.CreatePen(PS_SOLID,2,RGB(0,0,255));//曲线颜色pOldPen=pDC->SelectObject(&NewPen); double tStep=0.01;for(double t=0.0;t<=1.0;t+=tStep){ CP2 p(0.0,0.0);double denominator=0.0;for(int i=0;i<=n;i++){ double BValue=GetBasisFunctionValue(t,i,k); p+=P[i]*BValue*W[i];denominator+=BValue*W[i];}p/=denominator;//NURBS曲线定义if(0.0==t)pDC->MoveTo(ROUND(p.x),ROUND(p.y));elsepDC->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);
}void CGeometricfiguretestView::GetKnotVector()//哈德利-贾德方法获取节点值
{for(int i=0;i<=k;i++) //小于次数k的节点值为0knot[i]=0.0;for(int i=n+1;i<=n+k+1;i++)//大于n的节点值为1knot[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;}
}double CGeometricfiguretestView::GetBasisFunctionValue(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;elsereturn 0.0;}if(k>0){if(t<knot[i]||t>knot[i+k+1])return 0.0;else{double coffcient1,coffcient2;//凸组合系数1,凸组合系数2double denominator=0.0;//分母denominator=knot[i+k]-knot[i];//递推公式第一项分母if(denominator==0.0)//约定0/0coffcient1=0.0;elsecoffcient1=(t-knot[i])/denominator;denominator=knot[i+k+1]-knot[i+1];//递推公式第二项分母if(0.0==denominator)//约定0/0coffcient2=0.0;elsecoffcient2=(knot[i+k+1]-t)/denominator;value1=coffcient1*GetBasisFunctionValue(t,i,k-1);//递推公式第一项的值value2=coffcient2*GetBasisFunctionValue(t,i+1,k-1);//递推公式第二项的值value=value1+value2;//基函数的值}}return value;
}
五、NURBS曲面
5.1 曲面定义三种表达方式
5.1.1 曲面有理分式表示
5.1.2 曲面有理基函数表示
5.1.3 曲面齐次坐标表示
5.2 曲面构造
5.3 曲面基函数性质
5.4 曲面性质
5.5 曲面权因子
5.6 曲面代码实现
5.6.1 曲面类构造
#pragma once
#include"P3.h"class CNurbsSurface
{
public:CNurbsSurface(void);~CNurbsSurface(void);void Initialize(CP3** pPoint, double** pWeight,int m,int p,int n,int q);//初始化void GetKnotVector(double* T,int nCount,int num,int order, BOOL bU);//获取节点矢量void DrawNurbsSurface(CDC* pDC);//绘制曲面double BasisFunctionValue(double u,int i,int k,double* T);//根据节点矢量T,计算基函数void DrawControlGrid(CDC* pDC);//绘制控制网格CP2 ObliqueProjection(CP3 Point3);//斜二测投影
public:int m,p;//m为u向的顶点数减1,p为次数 int n,q;//n为v向的顶点数减1,q为次数CP3** P;//三维控制点double** W;//权因子double** U;//V为v向节点矢量数组double** V;//U为u向节点矢量数组
};
#include "StdAfx.h"
#include "NurbsSurface.h"
#include<math.h>
#define ROUND(d) int(d+0.5)
const double PI = 3.1415926;CNurbsSurface::CNurbsSurface(void)
{P=NULL;U=NULL;V=NULL;
}CNurbsSurface::~CNurbsSurface(void)
{if(NULL!=P){for(int i=0;i<n+1;i++){delete []P[i];P[i]=NULL;}delete []P;P=NULL;}if(NULL!=W){for(int i=0;i<n+1;i++){delete []W[i];W[i]=NULL;}delete []W;W = NULL;}if(NULL!=U){for(int i=0;i<n+1;i++){delete []U[i];U[i] = NULL;}delete []U;U=NULL;}if(NULL!=V){for(int i=0;i<m+1;i++){delete []V[i];V[i]=NULL;}delete []V;V=NULL;}
}void CNurbsSurface::Initialize(CP3** pPoint, double** pWeight,int m,int p,int n,int q)//曲面参数初始化
{P=new CP3* [n+1];//建立控制顶点的动态二维数组for(int i=0;i<n+1;i++)P[i]=new CP3[m+1];W=new double*[n+1];//建立权因子的动态二维数组for(int i=0;i<n+1;i++)W[i]=new double[m+1];for(int i=0;i<n+1;i++)//二维控制顶点数组初始化for(int j=0;j<m+1;j++)P[i][j]=pPoint[i][j];for(int i=0;i<n+1;i++)//二维权因子数组初始化for(int j=0;j<m+1;j++)W[i][j]=pWeight[i][j];this->m=m,this->p=p;this->n=n,this->q=q;U=new double* [n+1];//建立u向节点矢量动态数组for(int i=0;i<n+1;i++)U[i]=new double[m+p+2]; V=new double* [m+1];//建立v向节点矢量动态数组for(int i=0;i<m+1;i++)V[i]=new double[n+q+2];
}void CNurbsSurface::DrawNurbsSurface(CDC* pDC)//绘制曲面
{for(int i=0;i<n+1;i++)GetKnotVector(U[i],i,m,p, TRUE);//获取节点矢量Ufor(int i=0;i<m+1;i++)GetKnotVector(V[i],i,n,q, FALSE);//获取节点矢量V CPen NewPen(PS_SOLID,1,RGB(0,0,255));//曲线颜色CPen* pOldPen=pDC->SelectObject(&NewPen);double Step=0.1;//步长for(double u=0.0;u<=1.0;u+=Step){for(double v=0.0;v<=1.0;v+=Step){CP3 point(0,0,0);double weight=0.0;for(int i=0;i<n+1;i++){for(int j=0;j<m+1;j++){double BValueU=BasisFunctionValue(u,j,p,U[i]);double BValueV=BasisFunctionValue(v,i,q,V[j]);point+=P[i][j]*W[i][j]*BValueU*BValueV;weight+=W[i][j]*BValueU*BValueV;}}point/=weight;CP2 Point2=ObliqueProjection(point);//斜投影if (v==0.0)pDC->MoveTo(ROUND(Point2.x),ROUND(Point2.y));elsepDC->LineTo(ROUND(Point2.x),ROUND(Point2.y));}}for(double v=0.0;v<=1.0;v+=Step){for(double u=0.0;u<=1.0;u+=Step){CP3 point(0,0,0);double weight=0.0;for(int i=0;i<n+1;i++){for(int j=0;j<m+1;j++){double BValueU=BasisFunctionValue(u,j,p,U[i]);double BValueV=BasisFunctionValue(v,i,q,V[j]);point+=P[i][j]*W[i][j]*BValueU*BValueV;weight+=W[i][j]*BValueU*BValueV;}}point/=weight;CP2 Point2=ObliqueProjection(point);//斜投影if (u==0.0)pDC->MoveTo(ROUND(Point2.x),ROUND(Point2.y));elsepDC->LineTo(ROUND(Point2.x),ROUND(Point2.y));}}pDC->SelectObject(pOldPen);NewPen.DeleteObject();
}void CNurbsSurface::DrawControlGrid(CDC* pDC)//绘制控制网格
{CP2** P2=new CP2*[n+1];for(int i=0;i<n+1;i++)P2[i]=new CP2[m+1];for(int i=0;i<n+1;i++)for(int j=0;j<m+1;j++)P2[i][j]=ObliqueProjection(P[i][j]);CPen NewPen,*pOldPen;NewPen.CreatePen(PS_SOLID,1,RGB(255,255,0));pOldPen=pDC->SelectObject(&NewPen);CBrush NewBrush(RGB(0,0,0));CBrush* pOldBrush=pDC->SelectObject(&NewBrush);for(int i=0;i<n+1;i++)//绘制v向控制网格{pDC->MoveTo(ROUND(P2[i][0].x),ROUND(P2[i][0].y));pDC->Ellipse(ROUND(P2[i][0].x)-5,ROUND(P2[i][0].y)-5,ROUND(P2[i][0].x)+5,ROUND(P2[i][0].y)+5);for(int j=1;j<m+1;j++){pDC->LineTo(ROUND(P2[i][j].x),ROUND(P2[i][j].y));pDC->Ellipse(ROUND(P2[i][j].x)-5,ROUND(P2[i][j].y)-5,ROUND(P2[i][j].x)+5,ROUND(P2[i][j].y)+5);}}for(int j=0;j<m+1;j++)//绘制u向控制网格{pDC->MoveTo(ROUND(P2[0][j].x),ROUND(P2[0][j].y));for(int i=1;i<n+1;i++)pDC->LineTo(ROUND(P2[i][j].x),ROUND(P2[i][j].y));}pDC->SelectObject(pOldPen);NewPen.DeleteObject();if(NULL!=P2){for(int i=0;i<n+1;i++){delete []P2[i];P2[i]=NULL;}delete []P2;P2=NULL;}pDC->SelectObject(pOldPen);pDC->SelectObject(pOldBrush);
}void CNurbsSurface::GetKnotVector(double* T,int nCount,int num,int order, BOOL bU)//哈德利-贾德算法获取节点矢量数组
{for(int i=0;i<=order;i++) //小于等于曲线次数order的节点值为0T[i]=0.0;for(int i=num+1;i<=num+order+1;i++)//大于顶点数减1(n)的节点值为1T[i]=1.0;//计算num-order个内节点for(int i=order+1;i<=num;i++) {double sum=0.0;for(int j=order+1;j<=i;j++){ double numerator=0.0;//计算分子for(int loop=j-order;loop<=j-1;loop++){if(bU)//选择计算节点矢量U还是计算节点矢量Vnumerator+=(P[nCount][loop].x-P[nCount][loop-1].x)*(P[nCount][loop].x-P[nCount][loop-1].x)+(P[nCount][loop].y-P[nCount][loop-1].y)*(P[nCount][loop].y-P[nCount][loop-1].y);elsenumerator+=(P[loop][nCount].x-P[loop-1][nCount].x)*(P[loop][nCount].x-P[loop-1][nCount].x)+(P[loop][nCount].y-P[loop-1][nCount].y)*(P[loop][nCount].y-P[loop-1][nCount].y);}double denominator=0.0;//计算分母for(int loop1=order+1;loop1<=num+1;loop1++){for(int loop2=loop1-order;loop2<=loop1-1;loop2++){if(bU)denominator+=(P[nCount][loop2].x-P[nCount][loop2-1].x)*(P[nCount][loop2].x-P[nCount][loop2-1].x)+(P[nCount][loop2].y-P[nCount][loop2-1].y)*(P[nCount][loop2].y-P[nCount][loop2-1].y);elsedenominator+=(P[loop2][nCount].x-P[loop2-1][nCount].x)*(P[loop2][nCount].x-P[loop2-1][nCount].x)+(P[loop2][nCount].y-P[loop2-1][nCount].y)*(P[loop2][nCount].y-P[loop2-1][nCount].y);}} sum+=numerator/denominator; }T[i]=sum;}
}double CNurbsSurface::BasisFunctionValue(double t,int i,int order,double* T)//计算B样条基函数
{double value1,value2,value;if(order==0){if(t>=T[i] && t<T[i+1])return 1.0;elsereturn 0.0;}if(order>0){if(t<T[i]||t>T[i+order+1])return 0.0;else{double coffcient1,coffcient2;//凸组合系数1,凸组合系数2double denominator=0.0;//分母denominator=T[i+order]-T[i];//递推公式第一项分母if(denominator==0.0)//约定0/0coffcient1=0.0;elsecoffcient1=(t-T[i])/denominator;denominator=T[i+order+1]-T[i+1];//递推公式第二项分母if(0.0==denominator)//约定0/0coffcient2=0.0;elsecoffcient2=(T[i+order+1]-t)/denominator;value1=coffcient1*BasisFunctionValue(t,i,order-1,T);//递推公式第一项的值value2=coffcient2*BasisFunctionValue(t,i+1,order-1,T);//递推公式第二项的值value=value1+value2;//基函数的值}}return value;
}CP2 CNurbsSurface::ObliqueProjection(CP3 Point3)//斜二测投影
{CP2 Point2;Point2.x=Point3.x-Point3.z*sqrt(2.0)/2.0;Point2.y=Point3.y-Point3.z*sqrt(2.0)/2.0;return Point2;
}
5.6.2 曲面数据初始化
CP3** P3;//二维控制网格double** W;//权值int m,n;//u向和v向的控制点数-1int p,q;//u向和v向的次数CNurbsSurface NurbsSurface;//NURBS曲面对象CGeometricfiguretestView::CGeometricfiguretestView()
{// TODO: add construction code herem=4,p=3;//u向顶点数 5 个,曲线次数为 3 次n=4,q=3;//v向顶点数 5 个,曲线次数为 3 次P3=new CP3*[n+1]; //建立控制顶点的动态二维数组for(int i=0;i<n+1;i++)P3[i]=new CP3[m+1];W=new double*[n+1]; //建立权值的动态二维数组for(int i=0;i<n+1;i++)W[i]=new double[m+1];for(int i=0;i <n + 1;i++)for(int j=0;j<m+1;j++)W[i][j]=1;P3[0][0].x=82; P3[0][0].y=-100;P3[0][0].z=200;P3[0][1].x=-14; P3[0][1].y=65; P3[0][1].z=150;P3[0][2].x=-146;P3[0][2].y=94; P3[0][2].z=50;P3[0][3].x=-254;P3[0][3].y=80; P3[0][3].z=0;P3[0][4].x=-301;P3[0][4].y=40; P3[0][4].z=50;P3[1][0].x=147; P3[1][0].y=68; P3[1][0].z=150;P3[1][1].x=52; P3[1][1].y=117;P3[1][1].z=100;P3[1][2].x=-68; P3[1][2].y=146;P3[1][2].z=50;P3[1][3].x=-173;P3[1][3].y=135;P3[1][3].z=-10;P3[1][4].x=-238;P3[1][4].y=131;P3[1][4].z=-10;P3[2][0].x=227; P3[2][0].y=132;P3[2][0].z=140;P3[2][1].x=125; P3[2][1].y=154;P3[2][1].z=80;P3[2][2].x=0; P3[2][2].y=178;P3[2][2].z=30;P3[2][3].x=-109;P3[2][3].y=166;P3[2][3].z=-50;P3[2][4].x=-164;P3[2][4].y=174;P3[2][4].z=0;P3[3][0].x=337; P3[3][0].y=131;P3[3][0].z=150;P3[3][1].x=205; P3[3][1].y=173;P3[3][1].z=50;P3[3][2].x=75; P3[3][2].y=203;P3[3][2].z=0;P3[3][3].x=-51; P3[3][3].y=188;P3[3][3].z=-70;P3[3][4].x=-116;P3[3][4].y=178;P3[3][4].z=-100; P3[4][0].x=390; P3[4][0].y=50; P3[4][0].z=150;P3[4][1].x=288; P3[4][1].y=162;P3[4][1].z=50;P3[4][2].x=172; P3[4][2].y=208;P3[4][2].z=0;P3[4][3].x=48; P3[4][3].y=201;P3[4][3].z=-70; P3[4][4].x=-8; P3[4][4].y=200;P3[4][4].z=50;NurbsSurface.Initialize(P3,W,n,q,m,p);
}
5.6.3 调用绘制
void CGeometricfiguretestView::OnDraw(CDC* pDC)
{CTestDoc* pDoc = GetDocument();ASSERT_VALID(pDoc);if (!pDoc)return;// TODO: add draw code for native data hereCRect 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);//客户区中心为原点rect.OffsetRect(-rect.Width()/2,-rect.Height()/2);DrawGraph(pDC);//绘制图形
}void CGeometricfiguretestView::DrawGraph(CDC* pDC)//绘制NURBS曲面
{NurbsSurface.DrawNurbsSurface(pDC);NurbsSurface.DrawControlGrid(pDC);
}
你也可以改变权重查看效果