基于C++、OpenGL绘制贝塞尔曲线(Bézier curve)

本文主要参考了 计算机图形学8——Bezier curve(贝塞尔曲线) (https://blog.csdn.net/qq_41856733/article/details/85309521),如有侵权行为请联系作者:2803168649@qq.com。非常感谢文章提供的帮助。

第一次使用GL写C++程序,自我感觉良好(也是第一次写博客,刺激)。代码主要实现了在程序中随意点击画控制点,自动画出贝塞尔曲线,同时会显示各个点的坐标,控制点可拖动。代码中有详细的注释(我觉得挺详细的)。

算二项式系数时直接使用了累乘再累除,在网上搜索了解应该有更好的算法实现。

vs2019配置OpenGL可参考这篇文章:https://blog.csdn.net/weixin_41894030/article/details/100381865。

来张程序运行图,绿色的点是可以拖动的~
(其实有两个窗口,拼一起了)
在这里插入图片描述
程序代码如下:

/*
* ###############OpenGL绘制贝塞尔曲线(Bézier_curve)###############
* 
* 说明:
* 主要参考https://blog.csdn.net/qq_41856733/article/details/85309521
* GL相关函数可参考https://docs.microsoft.com/zh-cn/windows/win32/opengl/gllinestipple
* vs2019配置OpenGL可参考https://blog.csdn.net/weixin_41894030/article/details/100381865
* 如有侵权行为或其他不当之处欢迎联系作者:2803168649@qq.com
*/

#include <windows.h>
#include <GL/glut.h>
#include <math.h>
#include <stdio.h>

#define    MAXPOINTS    20


int* C;//二项式系数数组指针
int control_points_n = 0;//控制点数量
int specific_p;//特定点标志
int click_state = 0;//0为新增控制点,1为移动控制点,2为已到底控制点最大值
int screen_width, screen_height;//屏幕宽度,长度

struct point
{
    int x;
    int y;
};
point* P;


//函数声明
void reshape(int w, int h);//自适应屏幕窗口大小的改变
void keyboard(unsigned char button, int x, int y);//控制按键函数
void mouse_click(int button, int state, int x, int y);//处理鼠标click事件函数
void mouse_motion(int x, int y);//鼠标在窗口中按下并移动时调用此函数
void display(void);//显示函数,需要显示时调用此函数


//定位坐标函数
void gotoxy(int x, int y) {
    COORD coord;
    coord.X = x;
    coord.Y = y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

int main(int argc, char** argv)
{
    P = new point[MAXPOINTS];//控制点数组指针

    printf("按下\"ESC\"键退出。按下\"R\"键重新绘制\r\n");
    printf("最多添加20个点");

    glutInit(&argc, argv);//gult初始化

    /*在创建窗口的时候,指定其显示模式的类型。GLUT_DOUBLE代表双缓冲
    详见https://blog.csdn.net/yangyong0717/article/details/78003913*/
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(100, 100);//初始化窗口距离屏幕左边和上面的像素数
    glutInitWindowSize(500, 500);//初始化窗口宽度和高度
    glutCreateWindow("Bézier curve");//创建窗口,"Bézier curve"为窗口名字

    //清除颜色缓冲区,防止缓冲区中原有的颜色信息影响本次绘图
    //详见https://blog.csdn.net/jennifer111111/article/details/51649153
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glEnable(GL_LINE_STIPPLE);//启用直线点画功能
    glutReshapeFunc(reshape);//若屏幕窗口大小改变则调用reshape函数
    glutKeyboardFunc(keyboard);//处理普通按键消息。详见https://blog.csdn.net/xie_zi/article/details/1911891
    glutMouseFunc(mouse_click);//处理鼠标click事件
    glutMotionFunc(mouse_motion);//当鼠标在窗口中按下并移动时调用glutMotionFunc注册的回调函数
    glutDisplayFunc(display);//当窗口内容被绘制时调用display函数
    glutMainLoop();//进入gult死循环

    delete[] P;
    return 1;
}

//画正方形,用来标记点
void Draw_Square(int x, int y)
{
    glColor3f(0, 1, 0);//设置颜色,三个参数分别代表red, green, blue
    glBegin(GL_POLYGON);//绘制了一个凸多边形。顶点1到n定义了这个多边形。
    glVertex2f(x + 2, y + 2);
    glVertex2f(x - 2, y + 2);
    glVertex2f(x - 2, y - 2);
    glVertex2f(x + 2, y - 2);
    glEnd();
}

/*计算二项式系数
* n为次数,C为存放二项式系数的数组
* 利用了C(n-k)(n)=C(k)(n)
* 即C[k] = C(k)(n),k在右上角,n在左下角(不知道怎么打那个符号。。)*/
void Culculate_Binomial_Coefficient(int n, int* C)
{
    int k, j;

    for (k = 0;k <= n;k++)
    {
        C[k] = 1;
        for (j = n;j >= k + 1;j--)//1*n*(n-1)*(n-2)*...*(k+2)*(k+1)
        {
            C[k] *= j;
        }
        for (j = n-k;j >= 2;j--)//上式 / ( (n-k)*(n-k-1)*...*3*2*1 )
        {
            C[k] /= j;
        }
    }
}

//画贝塞尔曲线
//n为维度(即控制点数减1),p为控制点数组,m为密度
void Draw_Bézier_curve(int n, point* p, int m)
{
    int i, j;
    float t;
    int C[MAXPOINTS - 1];//二项式系数数组。这样定义有些浪费空间,但不知道更好的办法
    //算出来的曲线轨迹点x坐标。用float型相比int型曲线会更平滑
    float Bézier_curve_pointx;
    float Bézier_curve_pointy;

    Culculate_Binomial_Coefficient(n, C);
    glLineStipple(1, 0xffff);//设置为实线
    glBegin(GL_LINE_STRIP);
    //这里实际上是画了m条短直线来接近贝塞尔曲线
    for (i = 0;i <= m;i++)
    {
        t = (float)i / (float)m;
        Bézier_curve_pointx = 0;
        Bézier_curve_pointy = 0;
        for (j = 0;j < control_points_n;j++)
        {
            Bézier_curve_pointx += C[j] * pow(1 - t, control_points_n - j - 1) * pow(t, j) * p[j].x;
            Bézier_curve_pointy += C[j] * pow(1 - t, control_points_n - j - 1) * pow(t, j) * p[j].y;
        }
        glVertex2f(Bézier_curve_pointx, Bézier_curve_pointy);
    }
    glEnd();
}

//自适应屏幕窗口大小的改变
//关于reshape函数可以参考https://blog.csdn.net/qq_41856733/article/details/85309521
void reshape(int w, int h)
{
    screen_width = w;
    screen_height = h;

    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, w, 0.0, h);
}

//控制按键函数
void keyboard(unsigned char button, int x, int y) 
{
    switch (button)
    {
    case 82://"R"
    case 114://"r"
        //重新绘图
        //清空之前显示的坐标
        gotoxy(0, 3);
        for (int i = 0;i < control_points_n;i++)
        {
            printf("                   \r\n");
        }

        specific_p = control_points_n = 0;
        click_state = 0;

        /*glutPostRedisplay函数用来标记当前窗口需要重新绘制。通过glutMainLoop
        * 下一次循环时,窗口显示将被回调以重新显示窗口的正常面板*/
        glutPostRedisplay();
        break;
    case 27://"ESC"
        exit(0);//退出glut循环
    }
}

/*
* 处理鼠标click事件函数
* button表明哪个鼠标键被按下或松开,第二个参数表明函数被调用发生时鼠标的
* 状态(按下或松开),x,y表示鼠标在当前窗口的坐标(以左上角为原点)
*/
void mouse_click(int button, int state, int x, int y)
{
    int i;
    int distance;//鼠标点击处于任意一点距离

    //如果鼠标左键按下
    if (button == GLUT_LEFT_BUTTON)
    {
        //不是移动状态时计算距离
        if (state == GLUT_DOWN && click_state != 1)
        {
            for (i = 0;i < control_points_n;i++)
            {
                //按控制点依次计算距离
                distance = (x - P[i].x) * (x - P[i].x) +
                    (screen_height - y - P[i].y) * (screen_height - y - P[i].y);
                if (distance < 15)//如果距离小于特定值
                {
                    click_state = 1;//进入状态1,即移动点
                    specific_p = i;
                    break;//跳出循环
                }
            }
        }
        
        if (state == GLUT_DOWN && click_state == 0)//新增点
        {
            if (control_points_n < MAXPOINTS)
            {
                P[control_points_n].x = x;
                P[control_points_n].y = screen_height - y;
                specific_p = control_points_n;
                control_points_n++;
                glutPostRedisplay();
            }
            else click_state = 2;//进入状态2,无法新增控制点
        }
        else if (state == GLUT_DOWN && click_state == 1)//移动点
        {
            P[specific_p].x = x;
            P[specific_p].y = screen_height - y;
            glutPostRedisplay();
        }
        else if (state == GLUT_UP)
        {
            //判断控制点是否已达上限
            click_state = control_points_n < MAXPOINTS ? 0 : 2;
        }
    }
}

//鼠标在窗口中按下并移动时调用此函数
void mouse_motion(int x, int y)
{
    if (click_state == 1)
    {
        P[specific_p].x = x;
        P[specific_p].y = screen_height - y;
        glutPostRedisplay();
    }
}

//显示函数,需要显示时调用此函数
void display(void)
{
    int i;

    glClear(GL_COLOR_BUFFER_BIT);//清缓存。GL_COLOR_BUFFER_BIT代表当前可写的颜色缓冲

    for (i = 0;i < control_points_n;i++)//画控制点
    {
        Draw_Square(P[i].x, P[i].y);
    }
    //显示控制点坐标
    gotoxy(0, specific_p + 3);
    printf("P%d:(%d, %d)  ", specific_p, P[specific_p].x, P[specific_p].y);

    //0x0f0f即0000 1111 0000 1111,即黑白等长的虚线
    //glLineStipple函数详见https://blog.csdn.net/oktears/article/details/42262187
    glLineStipple(1, 0x0f0f);
    glBegin(GL_LINE_STRIP);//启用直线点画功能
    glColor3f(1, 1, 1);//设置颜色,三个参数分别代表red, green, blue
    for (i = 0;i < control_points_n;i++)//画虚线
    {
        glVertex2f(P[i].x, P[i].y);
    }
    glEnd();

    if (control_points_n > 1)//有大于1个控制点就画出贝塞尔曲线
    {
        Draw_Bézier_curve(control_points_n - 1, P, 200);
    }

    //函数详见https://blog.csdn.net/u012442207/article/details/45223261
    glutSwapBuffers();//交换两个缓冲区指针
}
  • 23
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值