OPENGL实现橡皮筋画图

目录

设计思路

关键部分

代码


最近学OpenGL的时候有一个要求是画图的时候实现橡皮筋效果,一开始觉得挺简单的,之前做过一个用Threejs实现的绘图,直接对Line对象改变坐标就能实现橡皮筋效果。比如直线ab,直接让每一次刷新时b的坐标跟随鼠标就行了。开始动手之后才发现和预想的不太一样,OpenGL里没法直接操作某一条线。

 

设计思路

看到有的博客里思路是:两点确定一条直线,a确定后,b与鼠标坐标相同,画线ab,鼠标移动后坐标为c,擦除ab,画ac。

前面没什么问题,但是仔细一想这里的擦除要怎么实现?那篇博客里是用反色实现的,但要是背景颜色并不是单一颜色怎么办?比如下图:

OPENGL实现橡皮筋画图_第1张图片

最后看了好多,找到了解决方法,思路和上面是一致的,只是“擦除”旧橡皮筋ab的方法不同。上面反色就好比是修修补补,而我们是直接推翻重来。

OPENGL实现橡皮筋画图_第2张图片

怎么个重来法呢

反色的思路是:画一条位置和ab一样,颜色与背景颜色一样的线,来吧旧橡皮筋ab覆盖掉

而我们的思路是:将整个画面清空,画出新的橡皮筋ac

OPENGL实现橡皮筋画图_第3张图片

可是既然绘制新橡皮筋ac时会清空画布,那么不也会同时删除原来已经画好的图吗??

当然会

前面确确实实的已经实现了橡皮筋效果,而我们为了实现橡皮筋效果而每次调用StartDraw都擦除了原来的画布,而要实现绘图不可能只画一条线只画一个多边形,所以在每次画完一个图形或者线之后都要将其数据存储起来,在每次StartDraw擦除画布后将其重绘出来。

最后写了个结构体记录已经画好的图形,用结构体数组存放已经画完的图形,感觉这样的话能顺便把撤销做了...

矩形、圆的橡皮筋也是同理了,还有一些没写完,贴一部分代码吧,明白过程也就很好写了

最终实现的橡皮筋效果:

OPENGL实现橡皮筋画图_第4张图片


关键部分

我们每次写openGL时候main函数里都有的glutDisplayFunc

void main(int argc, char ** argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	glutInitWindowPosition(400, 400);
	glutInitWindowSize(DefaultWindowWidth, DefaultWindowHeight);
	glutCreateWindow("Draw");

	init();
	glutDisplayFunc(StartDraw);
	glutReshapeFunc(changeSize);

	glutPassiveMotionFunc(onMouseMovePassive);//注册鼠标移动
	glutMouseFunc(onMouseDown);
	glutMainLoop();
}

glutDisplayFunc的参数(这里是StartDraw函数)是由openGL自动调用的,比如在StartDraw里面画了一个三角形,在程序刚开始的时候StartDraw就会被调用一次,程序启动起来我们就看到了那个绘制出来的三角形。要是我们改变了图像,又再旁边画了一个蛋,但是我们没有调用StartDraw,那么这个蛋是不会显示出来的。

看其他博客里说在以下几个条件时会调用StartDraw

1.   窗口内容绘制

2.   窗口大小改变

3.   窗口重绘

上面的几个条件没有测试过,但姑且能知道glutDisplayFunc的是调用参数里的函数来重新绘制画布的

 

main函数中注册鼠标移动

glutPassiveMotionFunc(onMouseMovePassive);

//鼠标移动
void onMouseMovePassive(int x, int y) {//坐标转换
	MouseX = x;
	MouseY = NowWindowHeight - y;
	
	//cout << "MouseX:" << MouseX << ",MouseY:" << MouseY<

我们在onMouseMovePassive函数中检查是否要启动橡皮筋效果,判定为true时使用glutPostRedisplay告诉openGL去重绘,每一次重绘在StartDraw中调用glClear将之前的橡皮筋“擦除”掉,绘制新的橡皮筋,就实现了橡皮筋效果。

(如果不glClear的话大概是这么个效果,每一条旧的橡皮筋都会保留,竟然有点好玩)

OPENGL实现橡皮筋画图_第5张图片

 

StartDraw函数里负责画橡皮筋的部分,其中DrawPointVertices[0]和DrawPointVertices[1]是线的第一个点的xy坐标(在鼠标第一次按下时确定),MouseX和MouseY是变换后的鼠标坐标

void StartDraw()
{
    glClear(GL_COLOR_BUFFER_BIT);//清空
	glColor3i(R,G,B);//设置颜色

	//实时跟随鼠标来实现橡皮筋效果
	if (NowDrawingState == FirstPoint) {
		if (DrawingLine) {//正在画直线
			glBegin(GL_LINES);
			glVertex2i(DrawPointVertices[0], DrawPointVertices[1]);
			glVertex2i(MouseX, MouseY);
			glEnd();
		}
	}
    glFlush();
}

而要重绘之前已经画好的其他图形,就要在StartDraw里,每次Clear之后将他们绘制出来

void StartDraw()
{
    glClear(GL_COLOR_BUFFER_BIT);//清空
	glColor3i(R,G,B);//设置颜色
	
    //绘制已经画好的图形
	for (int i = 0; i <= ElementArrayNum; i++) {
		int x1, y1,x2,y2;
		//设置颜色
		glColor3i( GraphElementArray[i].R,GraphElementArray[i].G,GraphElementArray[i].B);
		//根据记录的类型来选择对应的操作
		switch (GraphElementArray[i].type) {
		case Line:
			cout << "Line num:" << i << endl;
			//画线的函数
			x1 = GraphElementArray[i].x1;
			y1 = GraphElementArray[i].y1;
			x2 = GraphElementArray[i].x2;
			y2 = GraphElementArray[i].y2;
			DrawLine(x1,y1,x2,y2);
			break;
		case Rectangle:
			cout << "Polygon num:" << i << endl;
			//画多边形的函数
			x1 = GraphElementArray[i].x1;
			y1 = GraphElementArray[i].y1;
			x2 = GraphElementArray[i].x2;
			y2 = GraphElementArray[i].y2;
			DrawRectangle(GraphElementArray[i].RectangleMode,x1,y1,x2,y2);
			break;
		case Polygon:
            //===========
			break;
		default:
			cout << "something wrong in num:" << i << endl;
			break;
		}
	}

	//实时跟随鼠标来实现橡皮筋效果
	if (NowDrawingState == FirstPoint) {
		if (DrawingLine) {
			glBegin(GL_LINES);
			glVertex2i(DrawPointVertices[0], DrawPointVertices[1]);
			glVertex2i(MouseX, MouseY);
			glEnd();
		}
		if (DrawingRectangle) {
			glPolygonMode(GL_FRONT_AND_BACK,RectangeMode);
			glRecti(DrawPointVertices[0], DrawPointVertices[1],MouseX,MouseY);
		}
		if (DrawingPolygon) {
        //=======
		}
	}

    glFlush();
}

代码

如果你只是想测试一下橡皮筋效果而不在乎什么保留之前绘制好的图形的话,下面是实现橡皮筋的完整代码,没有“重绘已经完成的其他图形”功能,仅做演示


int DefaultWindowWidth = 500, DefaultWindowHeight = 500;//默认的窗口长宽
int NowWindowWidth = DefaultWindowWidth, NowWindowHeight = DefaultWindowHeight;//当前的窗口长宽

bool DrawingLine = true;//正在绘制直线

enum DrawingState { NonePoint, FirstPoint };//枚举绘制的所有阶段 SecondPoint要不要保留再考虑
static DrawingState NowDrawingState = NonePoint;//NowDrawingState 当前绘制阶段
int MouseX, MouseY;//变换之后的鼠标坐标

int DrawPointVertices[4];//存放两个顶点坐标

void init()
{
	glClearColor(1.0, 1.0, 1.0, 1.0);       //设置背景色 (R,G,B,alpha)
	glMatrixMode(GL_PROJECTION);            //投影
	gluOrtho2D(0.0, 200.0, 0.0, 150.0);		//(left right bottom top)
}

void StartDraw()
{
	glClear(GL_COLOR_BUFFER_BIT);           // 清空原颜色
	glColor3f(1.0, 0.0, 0.0);
	//实时跟随鼠标来实现橡皮筋效果
	if (NowDrawingState == FirstPoint) {
		if (DrawingLine) {
			glBegin(GL_LINES);
			glVertex2i(DrawPointVertices[0], DrawPointVertices[1]);
			glVertex2i(MouseX, MouseY);
			glEnd();
		}
	}

	glFlush();
}

//鼠标移动
void onMouseMovePassive(int x, int y) {//坐标转换
	MouseX = x;
	MouseY = NowWindowHeight - y;
	//cout << "MouseX:" << MouseX << ",MouseY:" << MouseY<

一点一点慢慢地摸索,图形学还是很有意思的

你可能感兴趣的:(计算机图形学)