k阶B样条曲线
\qquad
如上图,由于k次B样条曲线的控制点有k+1个,所以
P
0
P_0
P0
P
1
P_1
P1
P
2
P_2
P2
P
3
P_3
P3控制
u
1
u_1
u1
u
2
u_2
u2段曲线,
P
1
P_1
P1
P
2
P_2
P2
P
3
P_3
P3
P
4
P_4
P4控制
u
2
u_2
u2
u
3
u_3
u3段曲线,
P
2
P_2
P2
P
3
P_3
P3
P
4
P_4
P4
P
5
P_5
P5控制
u
3
u_3
u3
u
4
u_4
u4段曲线。
\qquad
公式为:
§
(
t
)
=
∑
j
=
i
−
k
i
P
j
⋅
N
j
,
k
(
t
)
\S(t) =\sum_{j=i-k}^i P_j·N_{j,k}(t)
§(t)=j=i−k∑iPj⋅Nj,k(t)
\qquad
S(t)表示的是
u
a
u_a
ua
u
a
u_a
ua
+
_+
+
1
_1
1段曲线,k表示的k阶B样条曲线,所以S(t)就是控制点与基函数的乘积之和,其中控制点是从
P
j
P_j
Pj点到
P
i
P_i
Pi点,一共有i-j+1个。结合图中的
u
1
u
2
u_1u_2
u1u2段曲线举个例子,
S
u
1
u
2
(
t
)
=
P
0
N
0
,
3
(
t
)
+
P
1
N
1
,
3
(
t
)
+
P
2
N
2
,
3
(
t
)
+
P
3
N
3
,
3
(
t
)
S_{u1u2}(t) = P_0N_{0,3}(t) + P_1N_{1,3}(t) + P_2N_{2,3}(t) + P_3N_{3,3}(t)
Su1u2(t)=P0N0,3(t)+P1N1,3(t)+P2N2,3(t)+P3N3,3(t)。这个公式比较抽象,我们换种写法,同时我们再给出N(t)的公式:
\qquad
j表示的是起始的控制点,k表示的是k次B样条曲线,i表示的是迭代参数,相当于控制点是从
P
j
P_j
Pj到
P
j
+
k
P_{j+k}
Pj+k。基函数N(t)中参数i,k跟其上面的公式保持同步。这个公式看起来很复杂,光用文字说明并不能解释太清楚,我们举一个二次B样条曲线和一个三次B样条曲线的例子来说明一下。
二阶B样条曲线
\qquad
二阶B样条曲线:k=2,下面是二次B样条曲线的三个基函数:
\qquad
这里我们跟据上面的基函数给出
P
0
,
2
(
t
)
P_{0,2}(t)
P0,2(t)的公式及相关性质:
\qquad
这条曲线的端点位置和端点切失如下
\qquad
根据上面的公式和性质可以得到如下曲线:
三阶B样条曲线
\qquad
三次B样条曲线:k=3。下面是三次B样条曲线的四个基函数:
\qquad
这里我们跟据上面的基函数给出
P
0
,
3
(
t
)
P_{0,3}(t)
P0,3(t)的公式及相关性质:
\qquad
这条曲线的端点位置和端点切失如下:
\qquad
根据上面的公式和性质可以得到如下曲线:
绘制三阶B样条曲线
实现效果
单击左键:创建控制点并绘制B样条曲线
单击右键:结束当前B样条曲线绘制,左键拖动控制点可修改B样条曲线
再次单击右键:开始绘制下一条B曲线
如图
拖动一个控制点
代码
#define GLUT_DISABLE_ATEXIT_HACK
#include <windows.h>
#include <math.h>
#include <gl/glut.h>
#include <iostream>
#define MAX_NUM_POINTS 50
#define MAX_LINE 100
using namespace std;
/*
使用说明:
单击左键:创建控制点并绘制B样条曲线
单击右键:结束当前B样条曲线绘制,左键拖动控制点可修改B样条曲线
再次单击右键:开始绘制下一条B曲线
*/
struct Point{
double x;
double y;
void SetPoint(int px, int py){
x = px;
y = py;
}
};
/*全局变量*/
Point vec[MAX_LINE][MAX_NUM_POINTS];//控制点
int point_num[MAX_LINE];//每条线的控制点数量
int line = 0;//线条下标
int num = 0;//控制点下标
bool mouseLeftDown = false;//监听鼠标左键
bool lineFinish = false;//监听画笔结束
/*绘制B样条曲线*/
void Bspline(int n){
float f1, f2, f3, f4;
float deltaT = 1.0 / n;
float T;
for (int k = 0; k <= line ; k++){
glBegin(GL_LINE_STRIP);
glLineWidth(20.0f);
glColor3f(1.0,0.0,0.0);
for (int i = 0; i < point_num[k]-3; i++){
for (int j = 0; j <= n; j++) {
T = j * deltaT;
f1 = (-T*T*T + 3*T*T - 3*T + 1) / 6.0;
f2 = (3*T*T*T - 6*T*T + 4) / 6.0;
f3 = (-3*T*T*T +3*T*T + 3*T + 1) / 6.0;
f4 = (T*T*T) / 6.0;
glVertex2f( f1*vec[k][i].x + f2*vec[k][i+1].x + f3*vec[k][i+2].x + f4*vec[k][i+3].x,
f1*vec[k][i].y + f2*vec[k][i+1].y + f3*vec[k][i+2].y + f4*vec[k][i+3].y);
}
}
glEnd();
}
}
/*用鼠标进行绘制,完成后可改变控制点,拖动即可*/
void display(){
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
//画直线
// glLineWidth(1.5f);
// glColor3f(1.0,0.0,0.0);
//
// for(int j = 0;j <= line; j++){
// glBegin(GL_LINE_STRIP);
// for(int i = 0;i < point_num[j]; i++){
// glVertex2f(vec[j][i].x, vec[j][i].y);
// }
// glEnd();
//画点
glPointSize(5.0f);
glColor3f(0.0, 0.0, 1.0);
glBegin(GL_POINTS);
for(int j = 0;j <= line; j++){
for(int i = 0;i < point_num[j]; i++){
glVertex2f(vec[j][i].x, vec[j][i].y);
}
}
glEnd();
//画B样条曲线
Bspline(20);
glFlush();
glutSwapBuffers();
}
/*初始化画布*/
void init() {
glClearColor(1.0, 1.0, 1.0, 0.0);//指定刷新颜色缓冲区时所用为白色
glShadeModel(GL_FLAT);//设置着色模式
}
/*重塑窗口*/
void reshape(int w, int h) {
glViewport(0, 0, (GLsizei)w, (GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, (GLsizei)w, (GLsizei)h, 0.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
/*计算鼠标落点和控制点的距离*/
double distance(int x1, int y1, int x2, int y2){
return sqrt((x1-x2) * (x1 -x2) + (y1-y2) * (y1-y2));
}
/*创建控制点*/
void mouse(int button, int state, int x, int y){
if (!lineFinish){//没画完
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN){//继续绘画
vec[line][num++].SetPoint(x, y);
point_num[line] ++;
glutPostRedisplay();//绘制
}
if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN){//结束绘画
line++;
num = 0;
lineFinish = true;
}
}else{//已画完
if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)//拖动控制点
mouseLeftDown = true;
if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
mouseLeftDown = false;
if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)//开启绘画
lineFinish = false;
}
}
/*移动控制点后重新绘制*/
void motion(int x, int y){
if(mouseLeftDown){
for (int i = 0; i <= line; i++){
for (int j = 0; j < point_num[i]; j++){
if(distance(vec[i][j].x, vec[i][j].y, x, y) < 20){
vec[i][j].SetPoint(x, y);
}
}
}
}
glutPostRedisplay();//绘制
}
int main(int argc,char** argv) {
glutInit(&argc,argv);//初始化glut库:打开窗口,管理窗口,从键盘、鼠标读取事件
glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE);//设置初始显示模式:指定 RGBA 颜色模式的窗口|指定双缓存窗口
glutInitWindowSize(1500, 750);//设置窗口大小
glutInitWindowPosition (200, 150);//设置窗口左上角的位置
glutCreateWindow("B-Spline Curve");//创建一个顶层窗口
init();
glutDisplayFunc(display);//注册绘图函数
glutReshapeFunc(reshape);//当窗的形状改变事件发生时 调用处理函数reshape
glutMouseFunc(mouse);//处理鼠标click事件
glutMotionFunc(motion);//鼠标移动并且有一个鼠标键被按下
glutMainLoop();//进入GLUT事件处理循环。最多调用一次,该函数永远不会返回.
return 0;
}
【注】:
1、推导过程参考:https://www.cnblogs.com/caster99/p/4746652.html
2、OpenGL可自行安装