【计算机图形学】【OpenGL】基于窗口的Liang-Barsky的折线段裁剪的实现

        计算机图形学课程实验,出于记录和分享学习过程的目的写下此博客。由于是第一次写博客,不足之处还望指出。

目录

实验要求:

实现效果:

头文件:

Liang-Barsky算法部分:

实现部分:

全局变量定义:

裁剪框的绘制:

折线绘制和其他键盘输入

 渲染函数:

最后是完整代码:


实验要求:

1.利用键盘橡皮筋技术交互绘制要裁剪的直线段,键盘'p'确定折线端点,键盘'e'结束折线绘制;

2.利用鼠标橡皮筋技术交互绘制裁剪窗口,鼠标左键单击确定裁剪窗口主对角线位置。

3.键盘‘c'实现基于窗口的直线裁剪

实现效果:

头文件:

定义了Point和Rect类型,分别为点和裁剪框(该部分老师提供)

#include <iostream>
#include <vector>
#include <windows.h>  
#include <glut.h>
using namespace std;
//点类型point
typedef struct Point {
	int x, y;
	Point(int a = 0, int b = 0)
	{
		x = a, y = b;
	}
} point;
//矩形类型rect
typedef struct Rectangle{
	float w_xmin,w_ymin;
	float w_xmax,w_yman; 
	Rectangle(float xmin = 0.0, float ymin = 0.0,float xmax=0.0,float yman=0.0){
		w_xmin = xmin;	w_ymin = ymin;	
		w_xmax = xmax;	w_yman = yman;
	}
}rect;
int Clip_Top(float p,float q,float &umax,float &umin);
void Line_Clipping(vector<point> &points,rect & winRect);

Liang-Barsky算法部分:

(该部分老师提供)

/***********************************************
*如果p参数<0,计算、更新umax,保证umax是最大u值
*如果p参数>0,计算、更新umin,保证umin是最小u值
*如果umax>umin,返回0,否则返回1
***********************************************/
int Clip_Top(float p, float q, float& umax, float& umin) {
	float r = 0.0;
	if (p < 0.0)	//线段从裁剪窗口外部延伸到内部,取最大值r并更新umax
	{
		r = q / p;
		if (r > umin) return 0;	//umax>umin的情况,弃之
		else if (r > umax)  umax = r;
	}
	else if (p > 0.0)      //线段从裁剪窗口内部延伸到外部,取最小值r并更新umin
	{
		r = q / p;
		if (r < umax) return 0;	//umax>umin的情况,弃之
		else if (r < umin)  umin = r;
	}
	else 		//p=0时,线段平行于裁剪窗口
		if (q < 0.0) return 0;
	return 1;
}
/*************************************************************
*已知winRect:矩形对象,存放标准裁剪窗口4条边信息
*    points:点的动态数组,存放直线2个端点信息
*根据裁剪窗口的左、右边界,求umax;
*根据裁剪窗口的下、上边界,求umin
*如果umax>umin,裁剪窗口和直线无交点,否则求裁剪后直线新端点
***************************************************************/
void Line_Clipping(vector<point>& points, rect& winRect) {  //(折线点数组,裁剪框)
	vector<point> new_points;								//存储裁剪结果
	for (int i = 0; i < iKeyPointNum - 1; i++)
	{
		while (new_points.size() <= 2 * i + 2)
		{
			new_points.push_back((0, 0));
		}
		point& p1 = points[i], & p2 = points[i + 1];
		float dx = p2.x - p1.x, dy = p2.y - p1.y, umax = 0.0, umin = 1.0;
		point p = p1;
		//比较左、右边界,获得最大的umax
		if (Clip_Top(-dx, p1.x - winRect.w_xmin, umax, umin))  //左边界
			if (Clip_Top(dx, winRect.w_xmax - p1.x, umax, umin)) //右边界
				//比较下、上边界,获得最小的umin
				if (Clip_Top(-dy, p1.y - winRect.w_ymin, umax, umin)) //下边界
					if (Clip_Top(dy, winRect.w_yman - p1.y, umax, umin)) //上边界
					{		//求裁剪后直线新端点	
						new_points[2 * i].x = (int)(p.x + umax * dx);
						new_points[2 * i].y = (int)(p.y + umax * dy);
						new_points[2 * i + 1].x = (int)(p.x + umin * dx);
						new_points[2 * i + 1].y = (int)(p.y + umin * dy);
					}
	}

	//结果赋值给原Vector
	for (int i = 0; i < new_points.size(); i++)
	{
		while (m_points.size() < new_points.size())
		{
			m_points.push_back((0, 0));
		}
		m_points[i] = new_points[i];
	}
}

 需要注意的是:教材中给出的原算法仅支持直线的裁剪,直接用裁剪得到的点覆盖了原Vector中的值;而对于折线段的裁剪,点的数量可能增加,因此不能直接覆盖。此处定义了新的Vector用于临时存储;同时也要注意防止溢出。

关于算法的原理可参考教材或其他文章,此处不作赘述。

实现部分:

主要思路如下:渲染函数中对动态数组m_points中的端点绘制成的直线,以及裁剪框进行渲染,键盘输入和Liang-Barsky算法则实时改变数组内端点的值;鼠标改变裁剪框的位置。

全局变量定义:

int iPointNum = 0, iKeyPointNum = 0;							//已经确定的点的数量
int xKey1 = 0, yKey1 = 0, xKey2 = 0, yKey2 = 0;					//记录当前折线点
int m_x1 = 0, m_x2 = 0, m_y1 = 0, m_y2 = 0;						//记录当前矩形点
int winWidth = 400, winHeight = 300;							//初始窗口大小
bool endDrow = false;											//是否结束折线输入
int ctimes = 0;													//是否进行了Liang_Barsky算法

vector<point> m_points;		//折线点数组
rect m_winRect;				//裁剪框

程序在不重新输入折线,仅改变裁剪框继续裁剪时会出现bug,笔者能力有限,研究过后也没能找出原因,因此用ctimes变量和reStart()函数强制刷新,防止连续裁剪。希望有大神能够帮助解答。

裁剪框的绘制:

在主函数中调用glutMouseFunc(MousePlot)和glutPassiveMotionFunc(PassiveMouseMove)来处理鼠标输入和鼠标移动;鼠标输入的回调函数中,我们要处理裁剪框的绘制,使用变量iPointNum记录鼠标点击次数,然后记录主对角线的坐标位置;右键可取消键入。

void MousePlot(GLint button, GLint action, GLint xMouse, GLint yMouse) {
	if (button == GLUT_LEFT_BUTTON && action == GLUT_DOWN) {
		if (iPointNum == 0 || iPointNum == 2) {
			iPointNum = 1;
			m_x1 = xMouse;
			m_y1 = winHeight - yMouse;
		}
		else
		{
			iPointNum = 2;
			m_x2 = xMouse;
			m_y2 = winHeight - yMouse;
			rect temp(min(m_x1, m_x2), min(m_y1, m_y2), max(m_x1, m_x2), max(m_y1, m_y2));		//创建裁剪框
			m_winRect = temp;
			glutPostRedisplay();
		}
		reStart();
		glutPostRedisplay();
	}
	if (button == GLUT_RIGHT_BUTTON && action == GLUT_DOWN) {
		iPointNum = 0;
		glutPostRedisplay();
	}
}

需要注意的是,在生成rect时,需要判断边的上下左右位置。

reStart()函数用于再次开始裁剪前的初始化。

//重新开始绘制,初始化各项数值
void reStart() {			
	endDrow = false;		
	iKeyPointNum = 0;
	m_points.clear();
	m_points.push_back((0, 0));
	ctimes = 0;
}

处理鼠标移动时的橡皮筋技术

void PassiveMouseMove(GLint xMouse, GLint yMouse) {
    //绘制裁剪框
	if (iPointNum == 1) {
		if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) {
			m_x2 = xMouse;
			m_y2 = m_y1 + m_x2 - m_x1;
		}
		else {
			m_x2 = xMouse;
			m_y2 = winHeight - yMouse;
		}
		glutPostRedisplay();
	}

    //绘制折线
	if (!endDrow) {
		xKey2 = xMouse;
		yKey2 = winHeight - yMouse;
		point temp = (xKey2, yKey2);
		m_points[iKeyPointNum] = temp;
		glutPostRedisplay();
	}
}

折线绘制和其他键盘输入

在主函数中调用glutKeyboardFunc(Key)处理键盘输入。输入p时,将当前鼠标坐标点存入数组;输入c时,执行执行Liang_Barsky算法;输入e时,结束折线的绘制。同时改变用于计数的变量的值。

void Key(unsigned char key, int x, int y) {
	switch (key) {
	case'p':			//确定折线端点
		if (!endDrow) {
			xKey1 = x;
			yKey1 = winHeight - y;
			//存入端点
			point temp(xKey1, yKey1);			
			if (m_points.size() < iKeyPointNum + 2) {
				m_points.push_back(temp);
			}
			m_points[iKeyPointNum] = temp;
			iKeyPointNum++;
			glutPostRedisplay();
		}
		else {
			reStart();
		}
		break;
	case'c':			//执行Liang_Barsky裁剪
		if (endDrow && iPointNum == 2&& ctimes==0) {
			Line_Clipping(m_points, m_winRect);
			ctimes = 1;
			glutPostRedisplay();
		}
		else {
			reStart();
		}
		break;
	case'e':			//结束绘制折线
		endDrow = true;
		glutPostRedisplay();
		break;
	default:break;
	}
}

 渲染函数:

主函数中调用glutDisplayFunc(Display)进行渲染。判断条件满足后,绘制裁剪框和折线段

void Display(void)
{
	glClear(GL_COLOR_BUFFER_BIT); //用当前背景色填充窗口
	glColor3f(1.0f, 0.0f, 0.0f); //设置当前的绘图颜色为红色

	//绘制矩形窗口
	if (iPointNum >= 1) {
		if (1) {
			glBegin(GL_LINE_LOOP);
			glVertex3f(m_x1, m_y1, 0.0);
			glVertex3f(m_x2, m_y1, 0.0);
			glVertex3f(m_x2, m_y2, 0.0);
			glVertex3f(m_x1, m_y2, 0.0);
			glEnd();
		}
	}

	//绘制折线段
	if (iKeyPointNum >= 1) {
		for (int i = 0; i < m_points.size() - 2; i++)
		{
			glBegin(GL_LINES);
			glVertex3f(m_points[i].x, m_points[i].y, 0.0);
			glVertex3f(m_points[i + 1].x, m_points[i + 1].y, 0.0);
			glEnd();
		}
		//动态显示当前绘制折线段(用数组则出现错误?)
		if (!endDrow) {
			glBegin(GL_LINES);
			glVertex3f(xKey1, yKey1, 0.0);
			glVertex3f(xKey2, yKey2, 0.0);
			glEnd();
		}
	}

	glutSwapBuffers(); 
}

在绘制随鼠标位置动态变化的当前折线段时,用(xKey1, yKey1, 0.0)和(xKey2, yKey2,0.0)则显示正常,用数组中的点坐标则出现错误,希望日后能够解决。

最后是完整代码:

#include <glut.h>
#include <stdio.h>
#include <vector>

using namespace std;
#include "lineClipping.h"

int iPointNum = 0, iKeyPointNum = 0;							//已经确定的点的数量
int xKey1 = 0, yKey1 = 0, xKey2 = 0, yKey2 = 0;					//记录当前折线点
int m_x1 = 0, m_x2 = 0, m_y1 = 0, m_y2 = 0;						//记录当前矩形点
int winWidth = 400, winHeight = 300;							//初始窗口大小
bool endDrow = false;											//是否结束折线输入
int ctimes = 0;													//是否进行了Liang_Barsky算法

vector<point> m_points;		//折线点数组
rect m_winRect;				//裁剪框

/***********************************************
*如果p参数<0,计算、更新umax,保证umax是最大u值
*如果p参数>0,计算、更新umin,保证umin是最小u值
*如果umax>umin,返回0,否则返回1
***********************************************/
int Clip_Top(float p, float q, float& umax, float& umin) {
	float r = 0.0;
	if (p < 0.0)	//线段从裁剪窗口外部延伸到内部,取最大值r并更新umax
	{
		r = q / p;
		if (r > umin) return 0;	//umax>umin的情况,弃之
		else if (r > umax)  umax = r;
	}
	else if (p > 0.0)      //线段从裁剪窗口内部延伸到外部,取最小值r并更新umin
	{
		r = q / p;
		if (r < umax) return 0;	//umax>umin的情况,弃之
		else if (r < umin)  umin = r;
	}
	else 		//p=0时,线段平行于裁剪窗口
		if (q < 0.0) return 0;
	return 1;
}
/*************************************************************
*已知winRect:矩形对象,存放标准裁剪窗口4条边信息
*    points:点的动态数组,存放直线2个端点信息
*根据裁剪窗口的左、右边界,求umax;
*根据裁剪窗口的下、上边界,求umin
*如果umax>umin,裁剪窗口和直线无交点,否则求裁剪后直线新端点
***************************************************************/
void Line_Clipping(vector<point>& points, rect& winRect) {		//(折线点数组,裁剪框)
	vector<point> new_points;								//存储裁剪结果
	for (int i = 0; i < iKeyPointNum - 1; i++)
	{
		while (new_points.size() <= 2 * i + 2)
		{
			new_points.push_back((0, 0));
		}
		point& p1 = points[i], & p2 = points[i + 1];
		float dx = p2.x - p1.x, dy = p2.y - p1.y, umax = 0.0, umin = 1.0;
		point p = p1;
		//比较左、右边界,获得最大的umax
		if (Clip_Top(-dx, p1.x - winRect.w_xmin, umax, umin))  //左边界
			if (Clip_Top(dx, winRect.w_xmax - p1.x, umax, umin)) //右边界
				//比较下、上边界,获得最小的umin
				if (Clip_Top(-dy, p1.y - winRect.w_ymin, umax, umin)) //下边界
					if (Clip_Top(dy, winRect.w_yman - p1.y, umax, umin)) //上边界
					{		//求裁剪后直线新端点	
						new_points[2 * i].x = (int)(p.x + umax * dx);
						new_points[2 * i].y = (int)(p.y + umax * dy);
						new_points[2 * i + 1].x = (int)(p.x + umin * dx);
						new_points[2 * i + 1].y = (int)(p.y + umin * dy);
					}
	}

	//结果赋值给原Vector
	for (int i = 0; i < new_points.size(); i++)
	{
		while (m_points.size() < new_points.size())
		{
			m_points.push_back((0, 0));
		}
		m_points[i] = new_points[i];
	}
}

void Display(void)
{
	glClear(GL_COLOR_BUFFER_BIT); //用当前背景色填充窗口
	glColor3f(1.0f, 0.0f, 0.0f); //设置当前的绘图颜色为红色

	//绘制矩形窗口
	if (iPointNum >= 1) {
		if (1) {
			glBegin(GL_LINE_LOOP);
			glVertex3f(m_x1, m_y1, 0.0);
			glVertex3f(m_x2, m_y1, 0.0);
			glVertex3f(m_x2, m_y2, 0.0);
			glVertex3f(m_x1, m_y2, 0.0);
			glEnd();
		}
	}

	//绘制折线段
	if (iKeyPointNum >= 1) {
		for (int i = 0; i < m_points.size() - 2; i++)
		{
			glBegin(GL_LINES);
			glVertex3f(m_points[i].x, m_points[i].y, 0.0);
			glVertex3f(m_points[i + 1].x, m_points[i + 1].y, 0.0);
			glEnd();
		}
		//动态显示当前绘制折线段(用数组则出现错误?)
		if (!endDrow) {
			glBegin(GL_LINES);
			glVertex3f(xKey1, yKey1, 0.0);
			glVertex3f(xKey2, yKey2, 0.0);
			glEnd();
		}
	}

	glutSwapBuffers(); 
}

void MousePlot(GLint button, GLint action, GLint xMouse, GLint yMouse) {
	if (button == GLUT_LEFT_BUTTON && action == GLUT_DOWN) {
		if (iPointNum == 0 || iPointNum == 2) {
			iPointNum = 1;
			m_x1 = xMouse;
			m_y1 = winHeight - yMouse;
		}
		else
		{
			iPointNum = 2;
			m_x2 = xMouse;
			m_y2 = winHeight - yMouse;
			rect temp(min(m_x1, m_x2), min(m_y1, m_y2), max(m_x1, m_x2), max(m_y1, m_y2));		//创建裁剪框
			m_winRect = temp;
			glutPostRedisplay();
		}
		reStart();
		glutPostRedisplay();
	}
	if (button == GLUT_RIGHT_BUTTON && action == GLUT_DOWN) {
		iPointNum = 0;
		glutPostRedisplay();
	}
}

//重新开始绘制,初始化各项数值
void reStart() {			
	endDrow = false;		
	iKeyPointNum = 0;
	m_points.clear();
	m_points.push_back((0, 0));
	ctimes = 0;
}
//动态显示当前线段
void PassiveMouseMove(GLint xMouse, GLint yMouse) {
	//绘制裁剪框
	if (iPointNum == 1) {			
		if (glutGetModifiers() == GLUT_ACTIVE_SHIFT) {
			m_x2 = xMouse;
			m_y2 = m_y1 + m_x2 - m_x1;
		}
		else {
			m_x2 = xMouse;
			m_y2 = winHeight - yMouse;
		}
		glutPostRedisplay();
	}

	//绘制折线
	if (!endDrow) {
		xKey2 = xMouse;
		yKey2 = winHeight - yMouse;
		point temp = (xKey2, yKey2);
		m_points[iKeyPointNum] = temp;
		glutPostRedisplay();
	}
}

void Key(unsigned char key, int x, int y) {
	switch (key) {
	case'p':			//确定折线端点
		if (!endDrow) {
			xKey1 = x;
			yKey1 = winHeight - y;
			//存入端点
			point temp(xKey1, yKey1);			
			if (m_points.size() < iKeyPointNum + 2) {
				m_points.push_back(temp);
			}
			m_points[iKeyPointNum] = temp;
			iKeyPointNum++;
			glutPostRedisplay();
		}
		else {
			reStart();
		}
		break;
	case'c':			//执行Liang_Barsky裁剪
		if (endDrow && iPointNum == 2&& ctimes==0) {
			Line_Clipping(m_points, m_winRect);
			ctimes = 1;
			glutPostRedisplay();
		}
		else {
			reStart();
		}
		break;
	case'e':			//结束绘制折线
		endDrow = true;
		glutPostRedisplay();
		break;
	default:break;
	}
}

void ChangeSize(int w, int h) {
	winWidth = w;
	winHeight = h;
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);					//设置投影参数(指定要控制的单元)
	glLoadIdentity();								//调用单位矩阵。去掉以前的投影参数设置
	gluOrtho2D(0.0, winWidth, 0.0, winHeight);		//设置投影参数
}

void Initial(void)
{
	glClearColor(1.0f, 1.0f, 1.0f, 1.0f); //设置窗口背景颜色为白色	
	m_points.push_back((0, 0));
}

int min(int a, int b) {
	if (a > b) {
		return b;
	}
	else {
		return a;
	}
}

int max(int a, int b) {
	if (a > b) {
		return a;
	}
	else {
		return b;
	}
}

int main(int argc, char* argv[])
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);				//初始化窗口的显示模式
	glutInitWindowSize(winWidth, winHeight);					//设置窗口的尺寸
	glutInitWindowPosition(100, 100);							//设置窗口的位置

	glutCreateWindow("橡皮筋技术");

	glutDisplayFunc(Display);									//设置当前窗口的显示回调函数
	glutReshapeFunc(ChangeSize);								//当窗口大小改变时函数被调用

	glutMouseFunc(MousePlot);
	glutKeyboardFunc(Key);
	glutPassiveMotionFunc(PassiveMouseMove);


	Initial();													//完成窗口初始化
	glutMainLoop();												//启动主GLUT事件处理循环
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值