系列文章目录

前文:OpenGL实现可交互的三次多项式曲线(控制鼠标可拖拽)




OpenGL实现可交互的三维三次贝塞尔曲线

  • 系列文章目录
  • 前言
  • 一、功能简介
  • 二、代码
  • 写在最后



前言

记录一次三维计算机图形学作业,继续提高自己的OpenGL编程能力。本次作业未实现旋转图形的功能,很遗憾。


鼠标在点下方移动可以令相应的控制点向下移动,同理上下左右,不是真正意义的拖拽效果

Android openGL 贝赛尔 opengl贝塞尔曲面_3d

一、功能简介

对于给定的四个点:Vertex p0(0, 0, 0);Vertex p1(2, 2, -2); Vertex c0(2, -1, -1);Vertex c1(4, 0, 0);求得它们对应的三维三次贝塞尔曲线。当左键在它们附近“拖拽”时,可以操控整条贝塞尔曲线的形状。

二、代码

代码如下(示例)请仔细阅读后,理解了代码原理再尝试运行:

#include <iostream>
#include<gl/glut.h>
#include<math.h>
#include<windows.h>
#include<algorithm>
using namespace std;
struct Vertex
{
    double x, y, z;
    Vertex(double tx, double ty, double tz)
    {
        x = tx;
        y = ty;
        z = tz;
    }
};

Vertex p0(0, 0, 0);        //点1 鼠标坐标系对应坐标100 300
Vertex p1(2, 2, -2);        //点2 鼠标坐标系对应坐标273 223
Vertex c0(2, -1, -1);       //点3 鼠标坐标系对应坐标255 340
Vertex c1(4, 0, 0);        //点4 鼠标坐标系对应坐标366 300

bool mouseLeftIsDown = false;     //在鼠标处于不同状态时,监测鼠标左键是否按下
bool mouseRightIsDown = false;    //监测鼠标右键是否按下

double caculateSquareDistance1(double x, double y, Vertex b)  //判断鼠标距离点的距离
{
    double conclusion = (x - 100 - b.x) * (x - 100 - b.x) + (y - 300 - b.y) * (y - 300 - b.y);
    //cout << x << " ," << y << endl; //取消注释本行,可以在console上显示当前鼠标坐标系对应坐标
    cout << "this is point1 p0:" << conclusion << endl;
    return conclusion;
}
double caculateSquareDistance2(double x, double y, Vertex b)  //判断鼠标距离点的距离
{
    double conclusion = (x - 330 - b.x) * (x - 300 - b.x) + (y - 200 - b.y) * (y - 200 - b.y);
    //cout << x << " ," << y << endl;
    cout << "this is point2 p1:" << conclusion << endl;
    return conclusion;
}
double caculateSquareDistance3(double x, double y, Vertex b)  //判断鼠标距离点的距离
{
    double conclusion = (x - 320 - b.x) * (x - 320 - b.x) + (y - 360 - b.y) * (y - 360 - b.y);
    //cout << x << " ," << y << endl;
    cout << "this is point3 c0:" << conclusion << endl;
    return conclusion;
}
double caculateSquareDistance4(double x, double y, Vertex b)  //判断鼠标距离点的距离
{
    double conclusion = (x - 500 - b.x) * (x - 500 - b.x) + (y - 300 - b.y) * (y - 300 - b.y);
    //cout << x << " ," << y << endl;
    cout << "this is point4 c1:" << conclusion << endl;
    return conclusion;
}
void mouse(int button, int state, int x, int y)       //监听鼠标动作
{
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        mouseLeftIsDown = true;
       
    }
    if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    {

        mouseLeftIsDown = false;
    }
    if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
    {

        mouseRightIsDown = true;
    }
    if (button == GLUT_RIGHT_BUTTON && state == GLUT_UP)
    {
        mouseRightIsDown = false;
    }
}

void motion0(int x, int y)       //移动点
{
    if (mouseLeftIsDown)        //左键移动控制点
    {
        if (caculateSquareDistance3(x, y, c0) < 5000)      //计算鼠标点击距离与点自身的距离大小
        {
            c0.x += 0.0006 * (x - 320) ;
            c0.y -= 0.0006 * (y - 360) ;
        
            glLoadIdentity();
            gluLookAt(1.5, 0, 2.0, 1.5, 0.0, 0.0, 0.0, 1.0, 0.0);
            glutPostRedisplay();        //重新构图
        }

        else if (caculateSquareDistance4(x, y, c1) < 5000)
        {
            c1.x += 0.0006 * (x - 500);
            c1.y -= 0.0006 * (y - 300);
            glLoadIdentity();
            gluLookAt(1.5, 0, 2.0, 1.5, 0.0, 0.0, 0.0, 1.0, 0.0);
            glutPostRedisplay();        //重新构图
        }
        else if (caculateSquareDistance1(x, y, p0) < 5000)
        {
            p0.x += 0.0006 * (x - 100);
            p0.y -= 0.0006 * (y - 300);
            glLoadIdentity();
            gluLookAt(1.5, 0, 2.0, 1.5, 0.0, 0.0, 0.0, 1.0, 0.0);
            glutPostRedisplay();        //重新构图
        }
        else if (caculateSquareDistance2(x, y, p1) < 5000)
        {
            p1.x += 0.0006 * (x - 330);
            p1.y -= 0.0006 * (y - 200);
            glLoadIdentity();
            gluLookAt(1.5, 0, 2.0, 1.5, 0.0, 0.0, 0.0, 1.0, 0.0);
            glutPostRedisplay();        //重新构图
        }
    }
    else if (mouseRightIsDown)      //右键
    {
    }

}
void drawCurve() { //绘制一个三维三次贝塞尔曲线
    glColor3f(0, 1.0f, 0);
    glPointSize(1.0);
    for (GLfloat t = 0; t <= 1.0; t += 0.001) {
        GLfloat x0 = p0.x * pow(1.0f - t, 3) + 3 * p1.x * t * pow(1.0f - t, 2) + 3 * c0.x * t * t * (1.0f - t) + c1.x * pow(t, 3);
        GLfloat y0 = p0.y * pow(1.0f - t, 3) + 3 * p1.y * t * pow(1.0f - t, 2) + 3 * c0.y * t * t * (1.0f - t) + c1.y * pow(t, 3);
        GLfloat z0 = p0.z * pow(1.0f - t, 3) + 3 * p1.z * t * pow(1.0f - t, 2) + 3 * c0.z * t * t * (1.0f - t) + c1.z * pow(t, 3);

        glBegin(GL_POINTS);
        glVertex3f(x0, y0, z0);
        glEnd();
    }
}
void reshape(int w, int h)

{
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);

    glMatrixMode(GL_PROJECTION);

    glLoadIdentity();

    gluPerspective(90, w / h, 0.5, 200);  //透视效果
 
    glMatrixMode(GL_MODELVIEW);
    gluLookAt(1.5, 0, 2.0, 1.5, 0.0, 0.0, 0.0, 1.0, 0.0);
    glLoadIdentity();

    glutPostRedisplay();        //重新构图
}
void draw0() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除颜色缓存与深度缓存
    gluLookAt(1.5, 0, 2.0, 1.5, 0.0, 0.0, 0.0, 1.0, 0.0);
   
    glPointSize(2.0f);
    glColor3f(0, 1, 1);
    //画出型值点和控制点(蓝色)
    glBegin(GL_POINTS);
    glVertex3d(p0.x, p0.y, p0.z);
    glVertex3d(p1.x, p1.y, p1.z);
    glVertex3d(c0.x, c0.y, c0.z);
    glVertex3d(c1.x, c1.y, c1.z);
    glEnd();

    glPointSize(4.0f);
    glColor3f(0, 0.7f, 1);
    glBegin(GL_POINTS);
    glVertex3d(0, 0, 0);
    glVertex3d(2, 2, -2);
    glVertex3d(2, -1, -1);
    glVertex3d(4, 0, 0);
    glEnd();

    //画出控制点与型值点的连线(红色)
    glColor3f(1, 0, 0);
    glLineWidth(3);
    glBegin(GL_LINES);
    glVertex3d(p0.x, p0.y, p0.z);
    glVertex3d(p1.x, p1.y, p1.z);
    glVertex3d(p1.x, p1.y, p1.z);
    glVertex3d(c0.x, c0.y, c0.z);
    glVertex3d(c0.x, c0.y, c0.z);
    glVertex3d(c1.x, c1.y, c1.z);

    glEnd();
    drawCurve();
    glutSwapBuffers();
}
int  main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    glutInitWindowPosition((GetSystemMetrics(SM_CXSCREEN) - 800) >> 1, (GetSystemMetrics(SM_CYSCREEN) - 600) >> 1);       //指定窗口位置
    glutCreateWindow("Ready to create 3D Bezier curve.");

    glutDisplayFunc(draw0);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutMotionFunc(motion0);
    glutMainLoop();

    return 0;
}

原本计划按下鼠标右键就可以使图形旋转,但由于时间关系没能实现,还望有能之士帮忙补充。


写在最后

本次实验最令人感到困惑的点,莫过于图形坐标系原点在屏幕中心,而鼠标坐标系原点在窗口左上角。笔者为这个问题困扰了数个小时之久,最终使用测得给定点在屏幕坐标系上的坐标后,在计算时与对应坐标分别相减,得到基本的控制效果。这还带来了第二个问题:每次移动控制点时,屏幕坐标系会一次减少几十个单位,而图形绘制时的单位都是个位数,过大的数值差令绘制效果很差,而且可能引发reshape时为将图形全部纳入屏幕而导致的视角缩放,从而再次改变屏幕坐标系。笔者最终选择在每次改动坐标时,对数值做一个倍率变化,从而使得数值变化不会过大。解决屏幕坐标系与绘图坐标系的矛盾最好的方式是建一个转换函数,使得鼠标每次改变位置都能转化为在绘图坐标系上投影的位置改变,笔者也没能实现,自感要学的还有很多。