对交互式绘制三次贝塞尔曲线的改进(二)——鼠标交互实现曲线的拼接
程序员文章站
2023-12-27 12:19:57
...
关于贝塞尔曲线在我的分类专栏的计算机图形学总结中有几篇文章分别给出了定义、递推公式、OpenGL贝塞尔曲线函数取实现贝塞尔曲线的绘制以及贝塞尔曲线的原理,至于交互式绘制三次贝塞尔曲线则是课后题,这次终于把这道课后题完整的实现了。
课后题:交互式绘制三次贝塞尔曲线,要求可以实现曲线的拼接,并据此验证贝塞尔曲线的凸包性、端点等性质。
-
贝塞尔曲线拼接的条件:
首先肯定是连接处的坐标相等了,在程序中有很好的体现。
其次是G1 连续性,也就是在交点处两个曲线的切线斜率相同,也就是前一条贝塞尔曲线的第三个控制点、交点、后一条贝塞尔曲线的第二个控制点共线。(但是我在代码中并没有加这条约束,主要还是为了实现连续画(想加只需要在补充对应点坐标计算出斜率看是否可以整除即可))
看下面这个例子理解: -
效果:
- 代码:
#include <GL/freeglut.h>
#include <vector>
using namespace std;
struct Line{
GLint x1,y1,x2,y2;
};//一条直线的结构体,包含两个端点
struct ControlP{
GLfloat x,y,z;
};//控制点的坐标值
Line line[100];//最多画100条连续折线
ControlP Point[33][4];//最多101个控制点也就是33个三次贝塞尔曲线,每个三次贝塞尔曲线有四个控制点
int PointNum=0,ControlNum=0,BezierNum=0,BezierControlNum=0;//端点个数,控制点个数,贝塞尔曲线个数,每条贝塞尔曲线当前控制点个数
int Width=400,Height=400;//屏幕的宽高
int flag=0;//需要标记是否已经开始交互画线
void myinit()
{
glClearColor(1.0,1.0,1.0,1.0);
}
void myReshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0,400.0,0.0,400.0);
}
void display()
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0,0.0,0.0);
glLineWidth(1.0);
int i=0;
for(i=0;i<PointNum;i++){
glBegin(GL_LINES);
glVertex2f(line[i].x1,line[i].y1);
glVertex2f(line[i].x2,line[i].y2);
glEnd();
}
if(flag==1){//用flag的厉害之处1,在每次移动鼠标事件重绘时最后一条曲线也需要画出来
glBegin(GL_LINES);
glVertex2f(line[PointNum].x1,line[PointNum].y1);
glVertex2f(line[PointNum].x2,line[PointNum].y2);
glEnd();
}
for(int j=0;j<BezierNum;j++){
GLfloat ControlPoint[4][3];
for(int m=0;m<4;m++){
ControlPoint[m][0]=Point[j][m].x;
ControlPoint[m][1]=Point[j][m].y;
ControlPoint[m][2]=Point[j][m].z;
}
glMap1f(GL_MAP1_VERTEX_3,0.0,1.0,3,4,*ControlPoint);//设定求值器
glEnable(GL_MAP1_VERTEX_3);//启动求值器
glColor3f(0.0f,0.0f,1.0f);
glLineWidth(4.0);
glMapGrid1f(100,0.0,1.0);//从0到1均匀生成100个参数值
glEvalMesh1(GL_LINE,0,100);//用折线的形式表示glMapGrid1f分成的0到100的值
}
//交换前后缓冲区
glutSwapBuffers();
}
//鼠标点击事件回调函数
void MousePlot(GLint button,GLint action,GLint xMouse,GLint yMouse)
{
if(button==GLUT_LEFT_BUTTON&&action==GLUT_DOWN){
//控制图形的端点
if(flag==0){//用flag的厉害之处2,判定开始交互,第一次选择的点仅被第一条直线占有,不用赋两次值
line[PointNum].x1=xMouse;
line[PointNum].y1=Height-yMouse;
flag=1;
}else{
line[PointNum].x2=xMouse;
line[PointNum].y2=Height-yMouse;
PointNum++;
line[PointNum].x1=xMouse;
line[PointNum].y1=Height-yMouse;
}
//控制点
Point[BezierNum][BezierControlNum].x=xMouse;
Point[BezierNum][BezierControlNum].y=Height-yMouse;
Point[BezierNum][BezierControlNum].z=0.0;
ControlNum++;
BezierControlNum++;
if(ControlNum%3==1&&ControlNum!=1){//因为画完第一条贝塞尔曲线后后面的因为需要实现连续,所以只需要画三个点
BezierNum++;
BezierControlNum=0;
Point[BezierNum][0].x=xMouse;
Point[BezierNum][0].y=Height-yMouse;
Point[BezierNum][0].z=0.0;
BezierControlNum++;
}
}
if(button==GLUT_RIGHT_BUTTON&&action==GLUT_DOWN){
PointNum--;
ControlNum--;
BezierNum--;
BezierControlNum--;
glutPostRedisplay();
}
}
//鼠标未按下时移动的回调函数
void PassiveMouseMove(GLint xMouse,GLint yMouse)
{
line[PointNum].x2=xMouse;
line[PointNum].y2=Height-yMouse;
glutPostRedisplay();
}
//主函数
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
//初始化OPENGL显示方式
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA);
//设定OPENGL窗口位置和大小
glutInitWindowSize (400, 400);
glutInitWindowPosition (100, 100);
//打开窗口
glutCreateWindow ("交互式绘制贝塞尔曲线");
//调用初始化函数
myinit();
//开始OPENGL的循环
glutDisplayFunc(display);
//设定窗口大小变化的回调函数
glutReshapeFunc(myReshape);
//鼠标响应函数
glutMouseFunc(MousePlot);
glutPassiveMotionFunc(PassiveMouseMove);
glutMainLoop();
return 0;
}