画Bezier曲线:鼠标获取、拖动控制点
本文是在前一篇绘制Bezier曲线的基础上加上了鼠标获取、拖动控制点的功能。主要练习OpenGL的鼠标操作。
鼠标操作内容转载自OpenGL鼠标操作 - 知乎 (zhihu.com)
鼠标操作函数为:
glutMouseFunc(mouse_hit);
glutMotionFunc(mouse_move);
鼠标操作函数括号里的mouse_hit,mouse_move的内容自己编写,当然名称也可以自定义。
mouse_hit函数:
void mouse_hit(int button, int state, int x, int y)
button:表示鼠标操作的是左键(GLUT_LEFT_BUTTON)、中键(GLUT_MIDDLE_BUTTON)、右键(GLUT_RIGHT_BUTTON),也可分别用整数0、1、2表示。
state:表示鼠标按键状态,包括按下(GLUT_DOWN)和抬起(GLUT_UP),也可分别用整数0和1表示
x,y:表示鼠标的坐标位置。
根据上述内容,鼠标点击函数只能获取x,y,所以我们也只能在二维平面里获取控制点、绘制Bezier曲线了。
mouse_move函数:
void mouse_move(int x, int y)
x,y:表示鼠标坐标位置。
注意设置一些全局变量来保存鼠标和控制点的信息:
int i = 0;//目前画出的控制点的个数 int total = 0;//控制点点的总个数 int mposition = 0;//移动的控制点在数组中的下标 int sta = 0;//鼠标是否按下 int button_kind = -1;//鼠标左右中键
利用以上两个鼠标函数和之前的Bezier绘制函数来实现功能:点击左键定位控制点,点击右键表示控制点绘制结束,绘制Bezier曲线。代码如下:
#include<GL/glut.h> #include<math.h> #include<array> typedef struct { int length; int* arr; }intArray;//系数坐标 typedef struct { float x, y; } point2D;//存储二维坐标 typedef struct { int length; point2D* arr; }pointArray2D; int i = 0;//画出的点的个数 int total = 0;//点的总个数 int mposition = 0;//移动的点在数组中的下标 int sta = 0;//鼠标是否按下 int button_kind = -1;//鼠标左右中键 pointArray2D ctrlPts2D_; point2D ctrlPts2D[7] = { }; //鼠标点击(左键确定控制点,右键表示绘制完成) void mouse_hit(int button, int state, int x, int y) { button_kind = button; switch (button) { case GLUT_LEFT_BUTTON: if (state == GLUT_DOWN) {//如果左键按下时 //将鼠标位置记录到控制点坐标数组中 if (total == 0) {//控制点还未绘制完成时 ctrlPts2D[i].x = (float)x / 100; ctrlPts2D[i].y = (float)y / 100; i++; } else if (total != 0) {//要改变控制点的坐标时 for (int j = 0; j < total; j++) { if ((fabs(ctrlPts2D[j].x - ((double)x / 100)) < 0.1) && (fabs(ctrlPts2D[j].y - ((double)y / 100)) < 0.1)) { mposition = j; ctrlPts2D[mposition].x = (float)x / 100; ctrlPts2D[mposition].y = (float)y / 100; sta = 1; } } } } else if (state = GLUT_UP ) {//如果左键从按下到抬起(结束移动控制点 ctrlPts2D[mposition].x = (float)x / 100; ctrlPts2D[mposition].y = (float)y / 100; mposition = -1; sta = 0; } break; case GLUT_RIGHT_BUTTON: total = i; } } //鼠标移动(拖动控制点以改变曲线形状) void mouse_move(int x, int y) { if (sta == 1) { float movex = (float)x / 100; float movey = (float)y / 100; ctrlPts2D[mposition].x = movex; ctrlPts2D[mposition].y = movey; } } //确定控制点,画出控制点和直线 void mouseclick() { //左键绘制控制点,右键或者中键控制点消失 if (button_kind == GLUT_LEFT_BUTTON) { glPointSize(10); glBegin(GL_POINTS); glColor3f(0.0f, 0.0f, 1.0f); for (int j = 0; j < i; j++) { glVertex2f(ctrlPts2D[j].x, ctrlPts2D[j].y); } glEnd(); if ((total == 0) || (total==i)) { glBegin(GL_LINE_STRIP); for (int j = 1; j < i; j++) { glVertex2f(ctrlPts2D[j - 1].x, ctrlPts2D[j - 1].y); glVertex2f(ctrlPts2D[j].x, ctrlPts2D[j].y); } } glEnd(); } } void deCasteljau(pointArray2D& ctrlPts, int precision) { int n = total; if (n < 2)return; float* xarray = new float[n - 1]; float* yarray = new float[n - 1]; float x = ctrlPts.arr[0].x; float y = ctrlPts.arr[0].y; for (int k = 0; k <= precision; k++) {//根据计算出的坐标连线precision次 float u = float(k) / float(precision); for (int i = 1; i < n; ++i) { for (int j = 0; j < n - i; ++j) { if (i == 1) {//第一次迭代根据控制点得出 xarray[j] = ctrlPts.arr[j].x * (1 - u) + ctrlPts.arr[j + 1].x * u; yarray[j] = ctrlPts.arr[j].y * (1 - u) + ctrlPts.arr[j + 1].y * u; } else {//通过上一次迭代结果计算 xarray[j] = xarray[j] * (1 - u) + xarray[j + 1] * u; yarray[j] = yarray[j] * (1 - u) + yarray[j + 1] * u; } } } glColor3f(0.0f, 1.0f, 1.0f); glBegin(GL_LINES); glVertex2f(x, y); glVertex2f(xarray[0], yarray[0]); glEnd(); x = xarray[0]; y = yarray[0]; } delete[] xarray; delete[] yarray; } void renderScene(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); gluOrtho2D(0, 8, 8, 0);//鼠标的窗口坐标以左上角为原点 mouseclick(); if (total != 0) { deCasteljau(ctrlPts2D_, 100); } //双缓存交换缓存以显示图像 glutSwapBuffers(); //每次更新显示 glutPostRedisplay(); } int main(int argc, char** argv) { ctrlPts2D_.length = total; ctrlPts2D_.arr = ctrlPts2D; glutInit(&argc, argv); //初始化glut glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); //设置窗口的模式-深度缓存,单缓存,颜色模型 glutInitWindowPosition(100, 100); //设置窗口的位置 glutInitWindowSize(800, 800); //设置窗口的大小 glutCreateWindow("3D Tech - GLUT Tutorial"); //创建窗口并赋予title glutDisplayFunc(renderScene);//调用renderScene把绘制传送到窗口 glutMouseFunc(mouse_hit); glutMotionFunc(mouse_move); glutMainLoop(); //进入事件循环等待 return 0; }
移动点时,首先要写OnMouseDown函数,以记录移动的控制点,
然后在鼠标移动(拖动)时利用OnMouseMove函数,来不断改变控制点的位置,
而最终在释放鼠标按钮时还要写OnMouseUP函数,来表示拖动结束
此外,由于用gluOrtho2D函数将窗口坐标限制得比较小,而获取的鼠标坐标为当前的窗口坐标(以左上角为原点),所以在获取鼠标坐标后都用除法缩小了坐标(gluOrtho2D的四个参数分别对应左、右、下、上的坐标,我输入的四个参数(0,8,8,0)正好将原点定在了左上角)。