實驗六:(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的縮寫,是一個預定義的常量,表示“不明確的數值結果”。