计算机图形学实验三 直线段的裁剪算法

实验三  直线段的裁剪算法

一、实验目的

1、理解二维图形的基本变换;

2、理解区域编码原理,掌握Cohen-Sutherland直线段裁剪算法;

3、理解Liang-Barsky算法的几何意义,掌握Liang-Barsky直线段裁剪算法。

二、实验性质

验证性

三、实验要求

1、认真阅读本次实验的目的,了解本次实验要求掌握的内容;

2、能够根据实验指导书的要求,完成相关的内容。

四、实验内容

1、根据Cohen-Sutherland裁剪算法原理,实现直线段的裁剪。

(1 ) 创建一个新的MFC应用程序,并且添加如下图所示的菜单,过程不再详细说明。

 

(2 ) 在CView.h中添加如下数据成员:   

protected:

     bool bDrawLine; //表示是否重绘直线

     CPoint P[2];      //直线两个端点

     int PtCount;    //端点个数

     int wxl,wxr,wyb,wyt; //窗口的四个边界

(3 ) 在CView.cpp中,给CView类构造函数添加如下代码:

CExp6View::CExp6View()

{

// TODO: 在此处添加构造代码

wxl=-300;wyt=100;wxr=300;wyb=-100; //窗口边界

PtCount=0;

bDrawLine=FALSE;

}

  (4 ) 在CView.cpp文件中,添加绘制剪裁窗口的函数DrawWindowRect(CDC* pDC),并且将该函数在CView.h中进行声明,DrawWindowRect(CDC* pDC)函数代码如下:

 void CExp6View::DrawWindowRect(CDC* pDC)//绘制裁剪窗口
{
	// TODO: Add your message handler code here and/or call default
	CPen *pOldPen;
	CPen NewPen(PS_SOLID,2,RGB(0,0,255)); //定义2个像素宽度的蓝色画笔
	pOldPen=pDC->SelectObject(&NewPen);
        pDC->Rectangle(wxl,wyt,wxr,wyb);
	pDC->SelectObject(pOldPen);
}

(5 )  在CView.cpp文件的onDraw()函数中,调用DrawWindowRect(CDC* pDC)绘制矩形剪裁窗口:

 void CExp6View::OnDraw(CDC* pDC)
{
	CExp6Doc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: 在此处为本机数据添加绘制代码
	//客户区窗口坐标变换
CRect rect; //定义客户区
	GetClientRect(&rect);//获得客户区的大小
	pDC->SetMapMode(MM_ANISOTROPIC);
	pDC->SetWindowExt(rect.Width(),rect.Height());
	pDC->SetViewportExt(rect.Width(),-rect.Height());
	pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2);
        
DrawWindowRect(pDC); //绘制剪裁窗口

if(PtCount==2)    //绘制直线段
	{
		pDC->MoveTo(P[0].x,P[0].y);
		pDC->LineTo(P[1].x,P[1].y);			
	}

}

(6 )  在CView.cpp文件中,为子菜单“绘制直线段”添加事件处理函数

 void CExp6View::OnDrawline()
{
	  // TODO: 在此添加命令处理程序代码	
PtCount=0;
	  bDrawLine=TRUE;
	  MessageBox(CString("使用鼠标左键设置直线端点"),CString("提示"),MB_OKCANCEL);
	  RedrawWindow();  // 重新刷新窗口	
}

(6 )  在CView.cpp文件中,为“CView”类的“WM_LBUTTONUP”消息添加事件处理函数:

 void CExp6View::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	if(bDrawLine)
	{	CRect rect;
	    GetClientRect(&rect);
	    CPoint temp;
	    temp.x=point.x-rect.Width()/2; //进行坐标括变换
	    temp.y=rect.Height()/2-point.y; 

	    if(PtCount<2) // 少于2个端点,则用左键选择端点
	    {
		P[PtCount]=temp;
		PtCount++;
	    }

RedrawWindow(); //重绘窗口
	}
	CView::OnLButtonDown(nFlags, point);
}

  (7 )  在CView.cpp文件中,添加端点编码的函数EnCode(CPoint pt),并且将该函数在CView.h中进行声明,EnCode(CPoint pt)函数代码如下:

#define LEFT   1   //代表二进制编码0001
#define RIGHT  2   //代表二进制编码0010
#define BOTTOM 4   //代表二进制编码0100
#define TOP    8   //代表二进制编码1000

      int CExp6View::EnCode(CPoint pt)//端点编码函数
{
	char rc=0;
	if(pt.x<wxl)
		rc=rc|LEFT;
	else if(pt.x>wxr)
		rc=rc|RIGHT;
	if(pt.y<wyb)
		rc=rc|BOTTOM;
	else if(pt.y>wyt)
		rc=rc|TOP;
	return rc;
}

(8 )  在CView.cpp文件中,为子菜单“Cohen-Sutherland裁剪算法”添加事件处理函数,效果如下图所示。

 void CExp6View::OnClipingCS() //Cohen-Sutherland算法
{
	// TODO: 在此添加命令处理程序代码
	CPoint p; //交点坐标
	char rc_p; //交点p的编码
	char rc0 = EnCode(P[0]); //起点编码
	char rc1 = EnCode(P[1]); //终点编码

	while (rc0 != 0 || rc1 != 0) //处理至少一个顶点在窗口之外的情况
	{
		if ((rc0 & rc1) != 0) //简弃之
		{
			PtCount = 0;
			break;
		}

		if (0 == rc0) //确保P[0]位于窗口之外
		{
			CPoint Pt; //交换端点
			Pt = P[0];
			P[0] = P[1];
			P[1] = Pt;

			char temp; //交换编码
			temp = rc0;
			rc0 = rc1;
			rc1 = temp;
		}

		char RC = rc0;
		double k = 1.0 * (P[1].y - P[0].y) / (P[1].x - P[0].x); //直线段的斜率

		//窗口边界按左、右、下、上的顺序裁剪直线段
		if (RC & LEFT) //与左边界相交
		{
			p.x = wxl;
			p.y = P[0].y + k * (wxl - P[0].x);
		}
		else if (RC & RIGHT) //与右边界相交
		{
			p.x = wxr;
			p.y = P[0].y + k * (wxr - P[0].x);
		}
		else if (RC & BOTTOM) //与下边界相交
		{
			p.y = wyb;
			p.x = P[0].x + (wyb - P[0].y) / k;
		}
		else if (RC & TOP) //与上边界相交
		{
			p.y = wyt;
			p.x = P[0].x + (wyt - P[0].y) / k;
		}

		rc_p = EnCode(p);
		P[0] = p; //将交点作为P[0]
		rc0 = rc_p;
	}

	bDrawLine = FALSE;
	RedrawWindow(); //重绘窗口
}

Cohen-Sutherland裁剪算法的运行截图如下:

(a)直线段裁剪前的图形

 

(b)直线段裁剪后的图形

 

2 、根据Liang-Barsky裁剪算法原理,实现直线段的裁剪。

(1 ) 在上述应用程序的CView.cpp文件中,为子菜单“Liang-Barsky裁剪算法”添加事件处理函数,实现效果如下图所示。

 void CExp6View::OnClipingLB()
{
	// TODO: 在此添加命令处理程序代码
	double tmax, tmin, dx, dy;
	dx = P[1].x - P[0].x;
	dy = P[1].y - P[0].y;
	tmax = 0.0;
	tmin = 1.0;

	// 计算u[i], v[i]
	double u[4] = { -dx, dx, -dy, dy };
	double v[4] = { P[0].x - wxl, wxr - P[0].x, P[0].y - wyb, wyt - P[0].y };

	// 遍历窗口四条边界,并根据u[i]的符号,判别第i条边界是入边还是出边,更新tmax或tmin
	for (int i = 0; i < 4; i++)
	{
		if (u[i] == 0) // 直线与边界平行
		{
			if (v[i] < 0) // 直线完全在边界外,舍弃
			{
				PtCount = 0;
				bDrawLine = FALSE;
				RedrawWindow();
				return;
			}
		}
		else
		{
			double t = v[i] / u[i];
			if (u[i] < 0) // 入边,更新tmax
			{
				if (t > tmax)
					tmax = t;
			}
			else // 出边,更新tmin
			{
				if (t < tmin)
					tmin = t;
			}
		}
	}

	// 检查是否有效裁剪
	if (tmax > tmin)
	{
		PtCount = 0;
		bDrawLine = FALSE;
		RedrawWindow();
		return;
	}

	// 计算裁剪后的新端点
	CPoint newP0, newP1;
	newP0.x = P[0].x + tmax * dx;
	newP0.y = P[0].y + tmax * dy;
	newP1.x = P[0].x + tmin * dx;
	newP1.y = P[0].y + tmin * dy;

	// 更新端点
	P[0] = newP0;
	P[1] = newP1;

	bDrawLine = FALSE;
	RedrawWindow();
}

Liang-Barsky裁剪算法运行截图如下:

(a ) 直线段裁剪前的图形

 

(b ) 直线段裁剪后的图形

五、思考题

1、在采用Cohen-Sutherland裁剪算法时,如何判断直线段是否与窗口边界(包括延长线)相交?以左边界为例进行说明。

答:①首先,给每个端点进行编码,编码规则如下:对于左边界,编码为 1,表示端点在左边界的左侧;对于右边界,编码为 2,表示端点在右边界的右侧;对于上边界,编码为 4,表示端点在上边界的上侧;对于下边界,编码为 8,表示端点在下边界的下侧

②对于与窗口左边界相交的情况:

   如果直线段的左端点编码为 1,右端点编码不为 1,则直线段与左边界相交,需要进行裁剪; 如果直线段的左端点编码不为 1,右端点编码为 1,则直线段与左边界相交,需要进行裁剪;如果直线段的左端点编码和右端点编码均为 1,表示直线段完全在左边界左侧,无需裁剪。

通过上述步骤,可以判断直线段是否与窗口左边界相交,并在需要裁剪的情况下进行相应的处理。

2、在采用Cohen-Sutherland裁剪算法时,如何求解直线段与窗口边界(包括延长线)的交点?

答“假设我们有直线段的两个端点坐标分别为(x_1, y_1)和(x_2, y_2),以及窗口左边界的方程为x = x_{min}。首先,我们需要确定直线段是否与窗口左边界相交。这可以通过比较直线段两个端点的x坐标与窗口左边界的x坐标来判断。如果两个端点都在窗口左边界的右侧,则直线段不与窗口左边界相交,不需要求解交点。

如果直线段与窗口左边界相交,我们可以通过直线的参数方程来求解交点的坐标。假设直线的参数方程为:

x = x_1 + t cdot (x_2 – x_1)

y = y_1 + t cdot (y_2 – y_1)

其中t是参数,t in [0, 1]表示交点在线段上的位置。将直线方程带入窗口左边界的方程x = x_{min}中,解出t的值,然后代入直线方程即可得到交点的坐标(x_{ ext{inter}}, y_{ ext{inter}})。

3、在采用Liang-Barsky裁剪算法时,如何判断窗口的边界是否为入边或出边? 如何求解入边与直线段的交点?如何求解出边与直线段的交点?

答:在Liang-Barsky裁剪算法中,要判断窗口边界是否为入边或出边,需要计算直线段的参数化表示式与窗口边界的交点,并检查这些交点的参数值。对于入边,参数值需满足 t0 <= t1,其中t0为入边交点的参数值,t1为出边交点的参数值。对于出边,参数值需满足 t0 > t1。要求解入边与直线段的交点,可使用参数化表示式计算直线段与窗口边界的交点,并取最大的t0值作为入边交点。求解出边与直线段的交点也是类似,但需取最小的t1值作为出边交点。

六、实验总结

      通过进行计算机图形学中直线段裁剪算法的实验,我深刻理解了二维图形的基本变换,并且掌握了Cohen-Sutherland直线段裁剪算法。理解了Liang-Barsky算法的几何意义,掌握了Liang-Barsky直线段裁剪算法。对课上的抽象理论的理解有了较好的帮助,总之,本次实验进一步了解了OpenGL图形渲染的原理和基本使用方法,收获很多。在实验中,我意识到区域编码原理的重要性,以及如何利用该原理对直线段进行裁剪,从而提高图形渲染效率。通过实践,我不仅理解了这些算法的原理和几何意义,而且掌握了它们的实际应用,为我深入学习和探索计算机图形学领域打下了坚实的基础。

 

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容