自定义绘制三阶B样条曲线

k阶B样条曲线

图1
\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=ikiPjNj,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可自行安装

  • 0
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值