系列文章目录
前文:OpenGL实现可交互的三次多项式曲线(控制鼠标可拖拽)
OpenGL实现可交互的三维三次贝塞尔曲线
- 系列文章目录
- 前言
- 一、功能简介
- 二、代码
- 写在最后
前言
记录一次三维计算机图形学作业,继续提高自己的OpenGL编程能力。本次作业未实现旋转图形的功能,很遗憾。
鼠标在点下方移动可以令相应的控制点向下移动,同理上下左右,不是真正意义的拖拽效果
一、功能简介
对于给定的四个点: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时为将图形全部纳入屏幕而导致的视角缩放,从而再次改变屏幕坐标系。笔者最终选择在每次改动坐标时,对数值做一个倍率变化,从而使得数值变化不会过大。解决屏幕坐标系与绘图坐标系的矛盾最好的方式是建一个转换函数,使得鼠标每次改变位置都能转化为在绘图坐标系上投影的位置改变,笔者也没能实现,自感要学的还有很多。