OPENGL实现橡皮筋画图

目录

设计思路

关键部分

代码


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

 

设计思路

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

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

背景不是单一颜色就不能利用反色

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

怎么个重来法呢

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

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

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

当然会

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

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

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

最终实现的橡皮筋效果:


关键部分

我们每次写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<<endl;
	
	//正在绘图 实现橡皮筋效果
	if (NowDrawingState == FirstPoint) {
		glutPostRedisplay();
	}
}

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

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

 

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<<endl;
	//正在绘图 实现橡皮筋效果
	if (NowDrawingState == FirstPoint) {
		glutPostRedisplay();
	}
}


//鼠标按下
void onMouseDown(int button, int state, int x, int y) {

	if (state == GLUT_DOWN) {//鼠标按下
		//正在画线
		if (DrawingLine) {
			if (NowDrawingState == NonePoint) {//一个点都没有画
				DrawPointVertices[0] = MouseX;
				DrawPointVertices[1] = MouseY;
				NowDrawingState = FirstPoint;
				return;
			}
			else if (NowDrawingState == FirstPoint) {//已经有了一个点
				DrawPointVertices[2] = MouseX;
				DrawPointVertices[3] = MouseY;

				NowDrawingState = NonePoint;//画线结束
				return;
			}
			else {
				return;
			}
		}
	}
}
//窗口大小改变时调用
void changeSize(GLsizei w, GLsizei h) {
	//保存当前窗口的大小     
	NowWindowWidth = w;
	NowWindowHeight = h;
	glViewport(0, 0, w, h);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, NowWindowWidth, 0.0, NowWindowHeight); 
}

int main(int argc, char ** argv)
{

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);  
	glutInitWindowPosition(400, 400);       

	glutInitWindowSize(DefaultWindowWidth, DefaultWindowHeight);
	glViewport(0,0,DefaultWindowWidth,DefaultWindowHeight);
	gluOrtho2D(1,1,1,1);
	glutCreateWindow("Draw"); 
	init();                                 
	glutDisplayFunc(StartDraw);
	glutReshapeFunc(changeSize);//窗口变化时使图形不会发生变形
	glutPassiveMotionFunc(onMouseMovePassive);//注册鼠标移动
	glutMouseFunc(onMouseDown);//鼠标点击
	glutMainLoop(); 
}

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

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值