分享

画Bezier曲线:鼠标获取、拖动控制点

 Bookroom for JetYang 2023-10-05 发布于湖南

本文是在前一篇绘制Bezier曲线的基础上加上了鼠标获取、拖动控制点的功能。主要练习OpenGL的鼠标操作。

鼠标操作函数为:

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)正好将原点定在了左上角)。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多