在产品零件设计中,许多自由曲面是通过自由曲线来构造的。对于自由曲线的设计,设计人员经常需要大致勾画出曲线的形状,用户希望有一种方法能不再采用一般的代数描述,而采用直观的具有明显几何意义的操作,使得设计的曲线能够逼近曲线的形状。采用插值方法,用户设计的曲线形状不但受曲线上型值点的约束,而且受到边界条件影响,用户不能灵活调整曲线形状。但在产品设计中,曲线的设计是经过多次修改和调整来完成的,已有的方法完成这样的功能并不容易。Bézier方法的出现改善了上述设计方法的不足,使用户能方便地实现曲线形状的修改。
一、Bézier曲线定义
1. 定义:用基函数表示的Bézier曲线为
Bernstein基函数:
当N=3时,即特征多边形有四个顶点,由上式可得到三次Bernstein基函数:
因此根据定义可把三次Bézier曲线表示为:
也可根据上式写成矩阵形式,有点类似二次型:
由上可知,只要给定特征多边形的四个顶点矢量P0P1P2P3,并得用上式即可构造一条三次Bézier曲线。只要改变t值即可计算曲线上的点。对于曲线设计来说,采用这种特征多边形顶点的方法比较方便、直观。
二、编程实现
利用OpenGL的glut库根据Bézier曲线定义来实现曲线的绘制,源程序如下:
// Bezier Curve Program
#include <gl\glut.h>
#define POINTS_NUM 100
// point structure
typedef struct SPoint{
float x;
float y;
}Point;
Point ctrlPoint[4];
void Initialize(void);
void DrawScene(void);
void myReshape(GLsizei w, GLsizei h);
void PlotBezierCurveDirectly(void);
Point PointOnBezierCurve(int i, int k, float t);
void PlotBezierCurve(void);
void main(int argc, char* argv[]) {
glutInit(&argc, argv); // Initialize GLUT
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB); // Set display mode
glutInitWindowPosition(50,100); // Set top-left display window position
glutInitWindowSize(400, 300); // set display window width and height
glutCreateWindow("Bézier Curve"); // Create display window
Initialize(); // Execute initialization procedure
glutDisplayFunc(DrawScene); // Send graphics to display window
glutReshapeFunc(myReshape); //
glutMainLoop(); // Display everything and wait
}
/*
*/
void Initialize(void) {
//glClearColor(1.0, 1.0, 1.0, 0.0); // Set Display-window color to white
glMatrixMode(GL_PROJECTION); // Set projection parameters
glLoadIdentity();
gluOrtho2D(0.0, 200, 0.0, 150); //
} // Initialize
/*
*/
void myReshape(GLsizei w, GLsizei h) {
// Reset viewport and projection parameter
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, w, 0, h);
glMatrixMode(GL_MODELVIEW);
} // myReshape
/*
*/
void DrawScene(void) {
glClear(GL_COLOR_BUFFER_BIT); // Clear display window
PlotBezierCurveDirectly();
PlotBezierCurve();
glFlush(); // Process all OpenGL routines as quickly possible
} // DrawScene
/*
plot the Bezier Curve according to Definition.
*/
void PlotBezierCurveDirectly(void) {
// Basis functions
#define B0(t) ((1.0 - t) * (1.0 - t) * (1.0 - t))
#define B1(t) (3.0 * t * (1.0 - t) * (1.0 - t))
#define B2(t) (3.0 * t * t * (1.0 - t))
#define B3(t) (t * t * t)
// initialize control points
Point controlPoint[4];
Point plotPoint[POINTS_NUM];
controlPoint[0].x = 100.0;
controlPoint[0].y = 100.0;
controlPoint[1].x = 300.0;
controlPoint[1].y = 200.0;
controlPoint[2].x = 400.0;
controlPoint[2].y = 200.0;
controlPoint[3].x = 600.0;
controlPoint[3].y = 100.0;
// calculate the points on the Bezier curve
glColor3f(1.0, 0.0, 0.0f); // set line segment geometry color to red
glBegin(GL_LINE_STRIP);
for (int i = 0; i < POINTS_NUM; ++i) {
float t = 1.0 * i / POINTS_NUM;
plotPoint[i].x = (B0(t) * controlPoint[0].x) +
(B1(t) * controlPoint[1].x) +
(B2(t) * controlPoint[2].x) +
(B3(t) * controlPoint[3].x);
plotPoint[i].y = (B0(t) * controlPoint[0].y) +
(B1(t) * controlPoint[1].y) +
(B2(t) * controlPoint[2].y) +
(B3(t) * controlPoint[3].y);
glVertex2f(plotPoint[i].x, plotPoint[i].y);
}
glEnd();
// draw control points
glColor3f(1.0, 1.0, 0.0f);
glPointSize(6.0);
glBegin(GL_POINTS);
glVertex2f(controlPoint[0].x, controlPoint[0].y);
glVertex2f(controlPoint[1].x, controlPoint[1].y);
glVertex2f(controlPoint[2].x, controlPoint[2].y);
glVertex2f(controlPoint[3].x, controlPoint[3].y);
glEnd();
// draw control polygon
glColor3f(1.0, 0.0, 1.0f);
glBegin(GL_LINE_STRIP);
glVertex2f(controlPoint[0].x, controlPoint[0].y);
glVertex2f(controlPoint[1].x, controlPoint[1].y);
glVertex2f(controlPoint[2].x, controlPoint[2].y);
glVertex2f(controlPoint[3].x, controlPoint[3].y);
glEnd();
} // PlotBezierCurveDirectly
/*
*/
Point PointOnBezierCurve(int i, int k, float t) {
if (k == 0) {
return ctrlPoint[i];
}
else {
Point point;
Point iPrev = PointOnBezierCurve(i, k-1, t);
Point iNext = PointOnBezierCurve(i+1, k-1, t);
point.x = (1.0 - t) * iPrev.x + t * iNext.x;
point.y = (1.0 - t) * iPrev.y + t * iNext.y;
return point;
}
} // PointOnBezierCurve
/*
Use recursive algorithm to plot Bezier Curve.
*/
void PlotBezierCurve(void) {
// initialize control points
ctrlPoint[0].x = 100.0;
ctrlPoint[0].y = 200.0;
ctrlPoint[1].x = 300.0;
ctrlPoint[1].y = 300.0;
ctrlPoint[2].x = 400.0;
ctrlPoint[2].y = 500.0;
ctrlPoint[3].x = 600.0;
ctrlPoint[3].y = 300.0;
Point plotPoint[POINTS_NUM];
// calculate the points on the Bezier curve
glColor3f(1.0, 0.0, 0.0f); // set line segment geometry color to red
glBegin(GL_LINE_STRIP);
for (int i = 0; i < POINTS_NUM; ++i) {
float t = 1.0 * i / POINTS_NUM;
plotPoint[i] = PointOnBezierCurve(0,3,t);
glVertex2f(plotPoint[i].x, plotPoint[i].y);
}
glEnd();
// draw control points
glColor3f(1.0, 1.0, 1.0f);
glPointSize(6.0);
glBegin(GL_POINTS);
glVertex2f(ctrlPoint[0].x, ctrlPoint[0].y);
glVertex2f(ctrlPoint[1].x, ctrlPoint[1].y);
glVertex2f(ctrlPoint[2].x, ctrlPoint[2].y);
glVertex2f(ctrlPoint[3].x, ctrlPoint[3].y);
glEnd();
// draw control polygon
glColor3f(0.0, 0.0, 1.0f);
glBegin(GL_LINE_STRIP);
glVertex2f(ctrlPoint[0].x, ctrlPoint[0].y);
glVertex2f(ctrlPoint[1].x, ctrlPoint[1].y);
glVertex2f(ctrlPoint[2].x, ctrlPoint[2].y);
glVertex2f(ctrlPoint[3].x, ctrlPoint[3].y);
glEnd();
} // PlotBezierCurve
程序运行结果如图1所示:
图1 根据定义实现的Bézier曲线
可修改特征多边形顶点变量controlPoint来看看相应的Bézier曲线的变化。
三、Bézier曲线的递推算法
计算Bézier曲线上的点,可用Bézier曲线方程直接计算,但使用de Casteljau提出的递推算法则要简单得多。
Bézier曲线的Bernstein基函数的递推性质
由于组合数的递推性:
因此有
为了保证公式可计算,我们约定:在以上公式中,凡当指标超出范围,以至记号不具有意义时,都应理解为零,如:
根据Bézier曲线定义可知:
这说明一条n次Bézier曲线可表示为分别由前后n个控制顶点决定的两条n-1次Bézier曲线的线性组合。由此可得Bézier曲线上某一点递归求值的de Casteljau算法:
其中:
——, 是定义Bézier曲线的控制点;
——,即是曲线P(t)上具有参数t的点;
用这一递推公式在给定参数下,求Bézier曲线上一点P(t)非常有效。de Casteljau算法稳定可靠,直观简便,可以编写出十分简捷的程序,是计算Bézier曲线的基本算法和标准算法。
四、de Casteljau算法的程序实现
根据递推公式可写出Bézier曲线的递归算法,如下:
/*
*/
Point PointOnBezierCurve(int i, int k, float t) {
if (k == 0) {
return ctrlPoint[i];
}
else {
Point point;
Point iPrev = PointOnBezierCurve(i, k-1, t);
Point iNext = PointOnBezierCurve(i+1, k-1, t);
point.x = (1.0 - t) * iPrev.x + t * iNext.x;
point.y = (1.0 - t) * iPrev.y + t * iNext.y;
return point;
}
} // PointOnBezierCurve
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
其中,为了保存数据的方便,控制顶点数组变量ctrlPoint为全局变量。
根据递推算法实现Bézier曲线与根据定义实现的程序效果如下图2所示:
图2 图中上面部分为用递推算法实现,下面部分为用定义实现。
源程序如下:
// Bezier Curve Program
// File : BezierCurve.cpp
#include <gl\glut.h>
#define POINTS_NUM 100
// point structure
typedef struct SPoint{
float x;
float y;
}Point;
Point ctrlPoint[4]; // control point for recursive algorithm Bezier Curve
void Initialize(void);
void DrawScene(void);
void myReshape(GLsizei w, GLsizei h);
void PlotBezierCurveDirectly(void);
Point PointOnBezierCurve(int i, int k, float t);
void PlotBezierCurve(void);
void main(int argc, char* argv[]) {
glutInit(&argc, argv); // Initialize GLUT
glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB); // Set display mode
glutInitWindowPosition(50,100); // Set top-left display window position
glutInitWindowSize(400, 300); // set display window width and height
glutCreateWindow("Bézier Curve"); // Create display window
Initialize(); // Execute initialization procedure
glutDisplayFunc(DrawScene); // Send graphics to display window
glutReshapeFunc(myReshape); //
glutMainLoop(); // Display everything and wait
}
/*
*/
void Initialize(void) {
//glClearColor(1.0, 1.0, 1.0, 0.0); // Set Display-window color to white
glMatrixMode(GL_PROJECTION); // Set projection parameters
glLoadIdentity();
gluOrtho2D(0.0, 200, 0.0, 150); //
} // Initialize
/*
*/
void myReshape(GLsizei w, GLsizei h) {
// Reset viewport and projection parameter
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, w, 0, h);
glMatrixMode(GL_MODELVIEW);
} // myReshape
/*
*/
void DrawScene(void) {
glClear(GL_COLOR_BUFFER_BIT); // Clear display window
PlotBezierCurveDirectly();
PlotBezierCurve();
glFlush(); // Process all OpenGL routines as quickly possible
} // DrawScene
/*
plot the Bezier Curve according to Definition.
*/
void PlotBezierCurveDirectly(void) {
// Basis functions
#define B0(t) ((1.0 - t) * (1.0 - t) * (1.0 - t))
#define B1(t) (3.0 * t * (1.0 - t) * (1.0 - t))
#define B2(t) (3.0 * t * t * (1.0 - t))
#define B3(t) (t * t * t)
// initialize control points
Point controlPoint[4];
Point plotPoint[POINTS_NUM];
controlPoint[0].x = 100.0;
controlPoint[0].y = 100.0;
controlPoint[1].x = 300.0;
controlPoint[1].y = 200.0;
controlPoint[2].x = 400.0;
controlPoint[2].y = 200.0;
controlPoint[3].x = 600.0;
controlPoint[3].y = 100.0;
// calculate the points on the Bezier curve
glColor3f(1.0, 0.0, 0.0f); // set line segment geometry color to red
glBegin(GL_LINE_STRIP);
for (int i = 0; i < POINTS_NUM; ++i) {
float t = 1.0 * i / POINTS_NUM;
plotPoint[i].x = (B0(t) * controlPoint[0].x) +
(B1(t) * controlPoint[1].x) +
(B2(t) * controlPoint[2].x) +
(B3(t) * controlPoint[3].x);
plotPoint[i].y = (B0(t) * controlPoint[0].y) +
(B1(t) * controlPoint[1].y) +
(B2(t) * controlPoint[2].y) +
(B3(t) * controlPoint[3].y);
glVertex2f(plotPoint[i].x, plotPoint[i].y);
}
glEnd();
// draw control points
glColor3f(1.0, 1.0, 0.0f);
glPointSize(6.0);
glBegin(GL_POINTS);
glVertex2f(controlPoint[0].x, controlPoint[0].y);
glVertex2f(controlPoint[1].x, controlPoint[1].y);
glVertex2f(controlPoint[2].x, controlPoint[2].y);
glVertex2f(controlPoint[3].x, controlPoint[3].y);
glEnd();
// draw control polygon
glColor3f(1.0, 0.0, 1.0f);
glBegin(GL_LINE_STRIP);
glVertex2f(controlPoint[0].x, controlPoint[0].y);
glVertex2f(controlPoint[1].x, controlPoint[1].y);
glVertex2f(controlPoint[2].x, controlPoint[2].y);
glVertex2f(controlPoint[3].x, controlPoint[3].y);
glEnd();
} // PlotBezierCurveDirectly
/*
*/
Point PointOnBezierCurve(int i, int k, float t) {
if (k == 0) {
return ctrlPoint[i];
}
else {
Point point;
Point iPrev = PointOnBezierCurve(i, k-1, t);
Point iNext = PointOnBezierCurve(i+1, k-1, t);
point.x = (1.0 - t) * iPrev.x + t * iNext.x;
point.y = (1.0 - t) * iPrev.y + t * iNext.y;
return point;
}
} // PointOnBezierCurve
/*
Use recursive algorithm to plot Bezier Curve.
*/
void PlotBezierCurve(void) {
// initialize control points
ctrlPoint[0].x = 100.0;
ctrlPoint[0].y = 300.0;
ctrlPoint[1].x = 300.0;
ctrlPoint[1].y = 400.0;
ctrlPoint[2].x = 400.0;
ctrlPoint[2].y = 400.0;
ctrlPoint[3].x = 600.0;
ctrlPoint[3].y = 300.0;
Point plotPoint[POINTS_NUM];
// calculate the points on the Bezier curve
glColor3f(1.0, 0.0, 0.0f); // set line segment geometry color to red
glBegin(GL_LINE_STRIP);
for (int i = 0; i < POINTS_NUM; ++i) {
float t = 1.0 * i / POINTS_NUM;
plotPoint[i] = PointOnBezierCurve(0,3,t);
glVertex2f(plotPoint[i].x, plotPoint[i].y);
}
glEnd();
// draw control points
glColor3f(1.0, 1.0, 1.0f);
glPointSize(6.0);
glBegin(GL_POINTS);
glVertex2f(ctrlPoint[0].x, ctrlPoint[0].y);
glVertex2f(ctrlPoint[1].x, ctrlPoint[1].y);
glVertex2f(ctrlPoint[2].x, ctrlPoint[2].y);
glVertex2f(ctrlPoint[3].x, ctrlPoint[3].y);
glEnd();
// draw control polygon
glColor3f(0.0, 0.0, 1.0f);
glBegin(GL_LINE_STRIP);
glVertex2f(ctrlPoint[0].x, ctrlPoint[0].y);
glVertex2f(ctrlPoint[1].x, ctrlPoint[1].y);
glVertex2f(ctrlPoint[2].x, ctrlPoint[2].y);
glVertex2f(ctrlPoint[3].x, ctrlPoint[3].y);
glEnd();
} // PlotBezierCurve
因此,根据递归算法绘制Bézier曲线的程序简单直观,便于把数学公式与程序对应。
图3 修改控制顶点后效果图