計算機圖形學 - 實驗6 - Cohen Sutherland裁剪算法

實驗六:2學時)


一、 實驗目的:

理解Cohen-Sutherland裁剪算法

 

二、 實驗內容:

實現Cohen-Sutherland編碼線段裁剪算法,能看到裁剪前後的屏幕顯示效果

 

三、 實現效果及步驟(或流程)


源碼如下:

// test6.cpp : 定義控制檯應用程序的入口點。
//

#include "stdafx.h"

#include<GL/glut.h>
#include<stdio.h>
#include<math.h> 

class Point
{
public:
	float x, y;
};

bool hasLine = true, cut = false;
float offset = 10;
int winWidth = 400, winHeight = 300;
Point clippingWindow[4], line[2], bound[4][2], inside[2];// 用於記錄線段line位於裁剪窗口內的兩個端點

// 初始化裁剪窗口的四個頂點座標
void initClippingWindow(void)
{
	// 左上角
	clippingWindow[0].x = -100;
	clippingWindow[0].y = 100;

	// 左下角
	clippingWindow[1].x = -100;
	clippingWindow[1].y = -100;

	// 右下角
	clippingWindow[2].x = 100;
	clippingWindow[2].y = -100;

	// 右上角
	clippingWindow[3].x = 100;
	clippingWindow[3].y = 100;
}

// 繪製裁剪窗口
void drawClippingWindow(void)
{
	glColor3f(0, 0, 0);
	glBegin(GL_LINE_LOOP);
	for (int i = 0; i < 4; i++)
		glVertex2f(clippingWindow[i].x, clippingWindow[i].y);
	glEnd();
}

// 更新邊界
void updateBound(void)
{
	// 由裁剪窗口的左上角頂點和左下角定點組成的線,即左邊界
	bound[0][0] = clippingWindow[0];
	bound[0][1] = clippingWindow[1];

	// 右邊界
	bound[1][0] = clippingWindow[2];
	bound[1][1] = clippingWindow[3];

	// 下邊界
	bound[2][0] = clippingWindow[1];
	bound[2][1] = clippingWindow[2];

	// 上邊界
	bound[3][0] = clippingWindow[0];
	bound[3][1] = clippingWindow[3];
}

// 初始化line
void initLine(void)
{
	line[0].x = 0;
	line[0].y = 0;

	line[1].x = 0;
	line[1].y = 0;

}

// 用指定的顏色畫線段
void drawLine(Point p1, Point p2, float red, float green, float blue)
{
	glLineWidth(5);
	glColor3f(red, green, blue);
	glBegin(GL_LINES);
	glVertex2f(p1.x, p1.y);
	glVertex2f(p2.x, p2.y);
	glEnd();
}

// 生成端點的區域碼
int encode(Point point, Point clippingWindow[4])
{
	int code = 0x0;

	if (point.x < clippingWindow[1].x)
		code = code | 0x1;
	if (point.x > clippingWindow[3].x)
		code = code | 0x2;
	if (point.y < clippingWindow[1].y)
		code = code | 0x4;
	if (point.y > clippingWindow[3].y)
		code = code | 0x8;

	return code;
}

// 求line1、line2的交點
Point getIntersection(Point line1[2], Point line2[2])
{
	float dx1 = line1[1].x - line1[0].x, dy1 = line1[1].y - line1[0].y;
	float dx2 = line2[1].x - line2[0].x, dy2 = line2[1].y - line2[0].y;
	Point intersection;

	if (dx1 != 0 && dx2 != 0)   // 如果兩條直線都有斜率
	{
		// 求直線的參數:y = ax + b
		float a1 = dy1 / dx1, b1 = line1[0].y - a1 * line1[0].x;
		float a2 = dy2 / dx2, b2 = line2[0].y - a2 * line2[0].x;

		intersection.x = (b2 - b1) / (a1 - a2);
		intersection.y = a1 * intersection.x + b1;
	}
	else if (dx1 == 0 && dx2 != 0)   // 如果line1垂直於x軸
	{
		float a2 = dy2 / dx2, b2 = line2[0].y - a2 * line2[0].x;

		intersection.x = line1[0].x;
		intersection.y = a2 * intersection.x + b2;
	}
	else if (dx1 != 0 && dx2 == 0)   // 如果line2垂直於x軸
	{
		float a1 = dy1 / dx1, b1 = line1[0].y - a1 * line1[0].x;

		intersection.x = line2[0].x;
		intersection.y = a1 * intersection.x + b1;
	}
	else   // 如果兩條直線都垂直於x軸,也就是平行,那麼無交點。(NAN = not a number)
	{
		intersection.x = NAN;
		intersection.y = NAN;
	}
	return intersection;
}

// Cohen-Sutherland線段裁剪算法
void cohenSutherland(Point clippingWindow[4], Point line[2], int mode)
{
	int code0 = encode(line[0], clippingWindow);
	int code1 = encode(line[1], clippingWindow);

	if (code0 == 0 && code1 == 0)   // 如果直線在裁剪窗口裏面,則直接繪製直線
		drawLine(line[0], line[1], 0, 1, 0);
	else
	{
		Point inside[2];   // 用於記錄線段line位於裁剪窗口內的兩個端點
		inside[0] = line[0];
		inside[1] = line[1];

		// 4次循環分別代表4個邊界處理:左、右、下、上
		for (int i = 0; i < 4; i++)
		{
			int temp = (int)pow(2, i);
			int current0 = (code0 & temp) >> i;
			int current1 = (code1 & temp) >> i;

			if (current0 == current1)   // 兩個端點都在邊界的同一側
			{
				if (current0 == 1)   // 兩個端點都在邊界的外側
				{
					if (mode == 0)
						drawLine(inside[0], inside[1], 1, 0, 0);
					return;
				}
				else   // 兩個端點都在邊界的內側
					continue;
			}
			else   // 兩個端點在邊界的兩側
			{
				Point p = getIntersection(inside, bound[i]);
				if (p.x != NAN && p.y != NAN)
				{
					if (current0 == 1)   // 端點inside[0]在邊界的外側,則用交點p換掉端點inside[0]
					{
						if (mode == 0)
							drawLine(p, inside[0], 1, 0, 0);
						inside[0] = p;
						code0 = encode(inside[0], clippingWindow);
					}
					else   // 端點inside[1]在邊界的外側,則用交點p換掉端點inside[1]
					{
						if (mode == 0)
							drawLine(p, inside[1], 1, 0, 0);
						inside[1] = p;
						code1 = encode(inside[1], clippingWindow);
					}
				}
			}
		}

		// 繪製裁剪窗口內的線段
		drawLine(inside[0], inside[1], 0, 1, 0);
	}
}

// 繪製裁剪窗口和線段
void display1(void)
{
	glClear(GL_COLOR_BUFFER_BIT);

	drawClippingWindow();
	cohenSutherland(clippingWindow, line,0);
	glFlush();
}

// 繪製裁剪窗口和線段
void display2(void)
{
	glClear(GL_COLOR_BUFFER_BIT);

	drawClippingWindow();
	cohenSutherland(clippingWindow, line,1);
	glFlush();
}

// 平移裁剪窗口
void translateClippingWindow(float tx, float ty)
{
	for (int i = 0; i < 4; i++)
	{
		clippingWindow[i].x += tx;
		clippingWindow[i].y += ty;
	}
	updateBound();
}

// 縮放裁剪窗口
void scaleClippingWindow(float sx, float sy)
{
	float centerX = (clippingWindow[1].x + clippingWindow[3].x) / 2;
	float centerY = (clippingWindow[1].y + clippingWindow[3].y) / 2;

	translateClippingWindow(-centerX, -centerY);
	for (int i = 0; i < 4; i++)
	{
		clippingWindow[i].x *= sx;
		clippingWindow[i].y *= sy;
	}
	translateClippingWindow(centerX, centerY);

	updateBound();
}

void mouseEvent(int button, int state, int clickX, int clickY)
{
	float x = clickX - (winWidth / 2), y = (winHeight / 2) - clickY;
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)   // 左鍵畫線
	{
		if (hasLine)
		{
			line[0].x = x;
			line[0].y = y;
			hasLine = false;
			cut = false;
		}
		else
		{
			line[1].x = x;
			line[1].y = y;
			hasLine = true;
		}
	}
	if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN && hasLine)   // 右鍵執行Cohen-Sutherland算法
	{
		cut = true;
		glutPostRedisplay();
	}
	if (button == 3)   // 向上滑動滾輪,放大裁剪窗口
	{
		cut = false;
		scaleClippingWindow(1.1, 1.1);
		glutPostRedisplay();
	}
	if (button == 4)   // 向下滑動滾輪,縮小裁剪窗口
	{
		cut = false;
		scaleClippingWindow(0.9, 0.9);
		glutPostRedisplay();
	}
}

void passiveMotionEvent(int clickX, int clickY)
{
	if (!hasLine)
	{
		line[1].x = clickX - (winWidth / 2);
		line[1].y = (winHeight / 2) - clickY;

		glutPostRedisplay();
	}
}

void keyEvent(unsigned char key, int clickX, int clickY)
{
	cut = false;

	float dx = 0, dy = 0;

	// w、a、s、d分別對應裁剪窗口的向上移動、向下移動、向左移動、向右移動操作
	if (key == 'w' || key == 'W')
		dy = offset;
	if (key == 's' || key == 'S')
		dy = -offset;
	if (key == 'a' || key == 'A')
		dx = -offset;
	if (key == 'd' || key == 'D')
		dx = offset;

	// Enter鍵:將裁剪窗口恢復回初始狀態
	if (key == 13)
		initClippingWindow();

	translateClippingWindow(dx, dy);

	glutPostRedisplay();
}

void main(int argc, char** argv)
{
	glutInit(&argc, argv);

	//form1
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	glutInitWindowPosition(50, 100);
	glutInitWindowSize(winWidth, winHeight);
	glutCreateWindow("Cohen Sutherland - 控制窗口");
	glClearColor(1, 1, 1, 0);
	glMatrixMode(GL_PROJECTION);
	gluOrtho2D(-winWidth / 2, winWidth / 2, -winHeight / 2, winHeight / 2);
	initClippingWindow();
	updateBound();
	initLine();
	glutDisplayFunc(display1);
	glutMouseFunc(mouseEvent);
	glutKeyboardFunc(keyEvent);
	glutPassiveMotionFunc(passiveMotionEvent);

	//form2
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	glutInitWindowPosition(500, 100);
	glutInitWindowSize(winWidth, winHeight);
	glutCreateWindow("Cohen Sutherland - 裁剪效果顯示窗口");
	glClearColor(1, 1, 1, 0);
	glMatrixMode(GL_PROJECTION);
	gluOrtho2D(-winWidth / 2, winWidth / 2, -winHeight / 2, winHeight / 2);
	initClippingWindow();
	updateBound();
	initLine();
	glutDisplayFunc(display2);

	glutMainLoop();
}

注:

NaN是Not a Number的縮寫,是一個預定義的常量,表示“不明確的數值結果”。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章