计算机图形学 - 实验:Two-Dimensional Viewing and Clippin
本博客基于课程"计算机图形学",教材使用为计算机图形学(第4版) [Computer Graphics with OpenGL, Fourth Edition],部分代码模板便来自于此教材,有所改动。大部分内容来自本人实验报告,有错误是难以避免的,若有表述错误或bug欢迎指出。
实验思路
编程思路
先设置裁剪窗口各条边的区域码,用十六进制表示,方便进行逻辑运算。encode()
函数用来对单个点相对于裁剪窗口的位置进行判断,并返回十六进制数。用accept()
,reject()
, inside()
函数依次表示完全在裁剪窗口内、完全在窗口外、穿过裁剪窗口。swapPts()
, swapCodes()
两函数作用分别是交换两点的坐标和两个二进制码。根据教材所给的line_clipping()
函数模板,变量done表示是否完成裁剪,变量poltLine
表示对判断线的接受或者丢弃,在while
循环中,依次用accept()
,reject()
, inside()
函数判断,当判断结果不为accept()
或reject()
时
进入第三个判断分支。在第三个判断分支中,先通过inside()
判断把code1
设置为窗口外的点;接着判断线段是否平行于窗口各边,若不平行则计算斜率,接着按照左右下上的顺序依次进行判断并裁剪,一次遍历裁剪完毕后,继续进行对code1
和code2
的赋值,继续进行判断,直到线段完全在窗口内或窗口外。对于裁剪窗口,设置全局变量矩形clip_rect
,通过Translaterect()
函数,对矩形的各边坐标进行增或减。再通过special_key()
函数,根据键盘输入的上下左右,调用Translaterect()
函数,从而实现移动裁剪窗口的功能
问题及解决方案
- 在创建裁剪窗口时,要把裁剪窗口设置为全局变量,以便在后面调用移动窗口函数中的修改;同样,设置裁剪窗口各边的区域码时,设置成全局常量,便于访问;
- 在
encode()
函数中,GLubyte
表示符号单字节整型,目的是为了得到一个十六进制的0/1串,便于后面进行逻辑操作。同时,不采用教材所给的(wcPt2D, wcPt2D, wcPt2D)
三参数的形式,而使用(CPoint2D pt, CRect *cw)
的两参数形式,并且由于在line_clipping()
函数中,传入的参数是矩形变量的指针,会修改矩形变量的属性,而encode()
的调用也在line_clipping()
函数中,所以即使在encode()
中不修改矩形的属性,也必须是传入矩形变量的指针。
实现代码
核心代码及关键步骤注释
class CRect {
public:
float xmin, ymin, xmax, ymax;
float width(void) { return xmax - xmin; }
float height(void) { return ymax - ymin; }
// Make (xmin, ymin) the lower left corner
void normalize(void);
// Draw the rectangle
void draw(GLenum mode);
};
void CRect::normalize(void) {
float ftemp;
if (xmin > xmax) {
ftemp = xmin;
xmin = xmax;
xmax = ftemp;
}
if (ymin > ymax) {
ftemp = ymin;
ymin = ymax;
ymax = ftemp;
}
}
void CRect::draw(GLenum mode) {
glBegin(mode);
glVertex2f(xmin, ymin);
glVertex2f(xmax, ymin);
glVertex2f(xmax, ymax);
glVertex2f(xmin, ymax);
glEnd();
}
int running_state = 0;
// 0 --- Normal state.
// 1 --- Rubber-band state.
// Size of the scene
float scene_size = 1000.0;
CRect clip_rect; // Clipping rectangle
CRect window; // Window
CRect viewport; // Viewport
// Program window size
int pw_width, pw_height;
const GLint left = 0x1;
const GLint right = 0x2;
const GLint bottom = 0x4;
const GLint top = 0x8;
GLint inside(GLint code) {
return GLint(!code);
}
GLint reject(GLint code1, GLint code2) {
return GLint(code1 & code2);
}
GLint accept(GLint code1, GLint code2) {
return GLint(!(code1 | code2));
}
GLubyte encode(CPoint2D pt, CRect *cw) {
GLubyte code = 0x00;
if (pt.x < cw->xmin)
code = code | left;
if (pt.x > cw->xmax)
code = code | right;
if (pt.y < cw->ymin)
code = code | bottom;
if (pt.y > cw->ymax)
code = code | top;
return (code);
}
void swapPts(CPoint2D* p1, CPoint2D* p2) {
CPoint2D temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void swapCodes(GLubyte* p1, GLubyte* p2) {
GLubyte temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
// Cohen-Sutherland line clipping algorithm.
bool line_clipping(CPoint2D p1, CPoint2D p2, CRect* cw,
CPoint2D* q1, CPoint2D* q2)
// p1, p2: End points of input line segment
// cw: Clipping rectangle
// q1, q2: End points of output line segment
// Return value: true --- accept, false --- reject
{
// Write your code here
GLubyte code1, code2;
GLint done = false, poltLine = false;
GLfloat m;
while (!done) {
code1 = encode(p1, cw);
code2 = encode(p2, cw);
if (accept(code1, code2)) {
done = true;
poltLine = true;
} else if (reject(code1, code2)) {
done = true;
} else {
if (inside(code1)) {
swapPts(&p1, &p2);
swapCodes(&code1, &code2);
}
if (p1.x != p2.x)
m = (p2.y - p1.y) / (p2.x - p1.x);
if (code1 & left) {
p1.y += (cw->xmin - p1.x) * m;
p1.x = cw->xmin;
} else if (code1 & right) {
p1.y += (cw->xmax - p1.x) * m;
p1.x = cw->xmax;
} else if (code1 & bottom) {
if (p2.x != p1.x)
p1.x += (cw->ymin - p1.y) / m;
p1.y = cw->ymin;
} else if (code1 & top) {
if (p2.x != p1.x)
p1.x += (cw->ymax - p1.y) / m;
p1.y = cw->ymax;
}
}
}
*q1 = p1;
*q2 = p2;
return poltLine;
}
//Translate the clip rectangle
void Translaterect(int dx, int dy) {
clip_rect.xmin += dx;
clip_rect.xmax += dx;
clip_rect.ymin += dy;
clip_rect.ymax += dy;
}
全部代码
// ====== Computer Graphics Experiment #6 ======
// | Two-Dimensional Viewing and Clipping |
// =============================================
//
// Requirement:
// (1) Implement Cohen-Sutherland line clipping algorithm.
// (2) Change position and size of window and viewport
// and observe the effects.
#include
#include
#include
// 2D point class
class CPoint2D {
public:
float x, y;
};
// Rectangle class
class CRect {
public:
float xmin, ymin, xmax, ymax;
float width(void) { return xmax - xmin; }
float height(void) { return ymax - ymin; }
// Make (xmin, ymin) the lower left corner
void normalize(void);
// Draw the rectangle
void draw(GLenum mode);
};
void CRect::normalize(void) {
float ftemp;
if (xmin > xmax) {
ftemp = xmin;
xmin = xmax;
xmax = ftemp;
}
if (ymin > ymax) {
ftemp = ymin;
ymin = ymax;
ymax = ftemp;
}
}
void CRect::draw(GLenum mode) {
glBegin(mode);
glVertex2f(xmin, ymin);
glVertex2f(xmax, ymin);
glVertex2f(xmax, ymax);
glVertex2f(xmin, ymax);
glEnd();
}
#define PI 3.14159265359
int running_state = 0;
// 0 --- Normal state.
// 1 --- Rubber-band state.
// Size of the scene
float scene_size = 1000.0;
CRect clip_rect; // Clipping rectangle
CRect window; // Window
CRect viewport; // Viewport
// Program window size
int pw_width, pw_height;
// Set window
void set_window(CRect* cw) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(cw->xmin, cw->xmax, cw->ymin, cw->ymax);
}
// Set viewport
void set_viewport(CRect* vp) {
glViewport(vp->xmin, vp->ymin,
vp->width(), vp->height());
}
const GLint left = 0x1;
const GLint right = 0x2;
const GLint bottom = 0x4;
const GLint top = 0x8;
GLint inside(GLint code) {
return GLint(!code);
}
GLint reject(GLint code1, GLint code2) {
return GLint(code1 & code2);
}
GLint accept(GLint code1, GLint code2) {
return GLint(!(code1 | code2));
}
GLubyte encode(CPoint2D pt, CRect* cw) {
GLubyte code = 0x00;
if (pt.x < cw->xmin)
code = code | left;
if (pt.x > cw->xmax)
code = code | right;
if (pt.y < cw->ymin)
code = code | bottom;
if (pt.y > cw->ymax)
code = code | top;
return (code);
}
void swapPts(CPoint2D* p1, CPoint2D* p2) {
CPoint2D temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void swapCodes(GLubyte* p1, GLubyte* p2) {
GLubyte temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
// Cohen-Sutherland line clipping algorithm.
bool line_clipping(CPoint2D p1, CPoint2D p2, CRect* cw,
CPoint2D* q1, CPoint2D* q2)
// p1, p2: End points of input line segment
// cw: Clipping rectangle
// q1, q2: End points of output line segment
// Return value: true --- accept, false --- reject
{
// Write your code here
GLubyte code1, code2;
GLint done = false, poltLine = false;
GLfloat m;
while (!done) {
code1 = encode(p1, cw);
code2 = encode(p2, cw);
if (accept(code1, code2)) {
done = true;
poltLine = true;
} else if (reject(code1, code2)) {
done = true;
} else {
if (inside(code1)) {
swapPts(&p1, &p2);
swapCodes(&code1, &code2);
}
if (p1.x != p2.x)
m = (p2.y - p1.y) / (p2.x - p1.x);
if (code1 & left) {
p1.y += (cw->xmin - p1.x) * m;
p1.x = cw->xmin;
} else if (code1 & right) {
p1.y += (cw->xmax - p1.x) * m;
p1.x = cw->xmax;
} else if (code1 & bottom) {
if (p2.x != p1.x)
p1.x += (cw->ymin - p1.y) / m;
p1.y = cw->ymin;
} else if (code1 & top) {
if (p2.x != p1.x)
p1.x += (cw->ymax - p1.y) / m;
p1.y = cw->ymax;
}
}
}
*q1 = p1;
*q2 = p2;
return poltLine;
}
//Translate the clip rectangle
void Translaterect(int dx, int dy) {
clip_rect.xmin += dx;
clip_rect.xmax += dx;
clip_rect.ymin += dy;
clip_rect.ymax += dy;
}
// Initialization function
void init(void) {
glClearColor(0.0, 0.0, 0.0, 0.0);
glEnable(GL_LINE_STIPPLE);
}
// Display callback function
void display(void) {
int i;
CPoint2D p1, p2, q1, q2;
double a, r;
glClear(GL_COLOR_BUFFER_BIT);
// Draw blue rectangle to fill the background
glColor3f(0.0, 0.0, 0.5);
window.draw(GL_POLYGON);
// Draw unclipped lines in green color
p1.x = 0.0;
p1.y = 0.0;
r = 0.5 * scene_size;
glLineStipple(1, 0x0f0f);
glColor3f(0.0, 1.0, 0.0);
glBegin(GL_LINES);
for (i = 0; i < 360; i += 15) {
a = (double)i / 180.0 * PI;
p2.x = r * cos(a);
p2.y = r * sin(a);
if (i == 0 || i == 180)
p2.y = 0;
if (i == 90 || i == 270)
p2.x = 0;
p2.x += p1.x;
p2.y += p1.y;
glVertex2f(p1.x, p1.y);
glVertex2f(p2.x, p2.y);
}
glEnd();
glLineStipple(1, 0xffff);
// Draw clipped lines in white color
if (running_state == 0) {
glColor3f(1.0, 1.0, 1.0);
glLineWidth(2.0);
glBegin(GL_LINES);
for (i = 0; i < 360; i += 15) {
a = (double)i / 180.0 * PI;
p2.x = r * cos(a);
p2.y = r * sin(a);
if (i == 0 || i == 180)
p2.y = 0;
if (i == 90 || i == 270)
p2.x = 0;
p2.x += p1.x;
p2.y += p1.y;
if (line_clipping(p1, p2, &clip_rect, &q1, &q2)) {
glVertex2f(q1.x, q1.y);
glVertex2f(q2.x, q2.y);
}
}
glEnd();
glLineWidth(1.0);
}
// Draw clipping rectangle
glLineStipple(1, 0x0f0f);
glColor3f(1.0, 1.0, 0.0);
clip_rect.draw(GL_LINE_LOOP);
glLineStipple(1, 0xffff);
glutSwapBuffers();
}
// Reshape callback function
void reshape(int w, int h) {
// Store program window size
pw_width = w;
pw_height = h;
// set viewport
viewport.xmin = 0;
viewport.xmax = w;
viewport.ymin = 0;
viewport.ymax = h;
set_viewport(&viewport);
// set clipping window
window.xmin = -0.6 * scene_size;
window.xmax = 0.6 * scene_size;
window.ymin = -0.6 * scene_size;
window.ymax = 0.6 * scene_size;
set_window(&window);
// set clipping rectangle
clip_rect.xmin = 0.5 * window.xmin;
clip_rect.xmax = 0.5 * window.xmax;
clip_rect.ymin = 0.5 * window.ymin;
clip_rect.ymax = 0.5 * window.ymax;
}
// Keyboard callback function
void keyboard(unsigned char key, int x, int y) {
switch (key) {
case 27:
exit(0);
}
}
// Special keyboard callback function
void special_key(int key, int x, int y) {
switch (key) {
case GLUT_KEY_LEFT:
Translaterect(-5.0, 0.0);
glutPostRedisplay();
break;
case GLUT_KEY_RIGHT:
Translaterect(5.0, 0.0);
glutPostRedisplay();
break;
case GLUT_KEY_DOWN:
Translaterect(0.0, -5.0);
glutPostRedisplay();
break;
case GLUT_KEY_UP:
Translaterect(0.0, 5.0);
glutPostRedisplay();
break;
}
}
// Main program entrance
int main(int argc, char* argv[]) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutInitWindowSize(800, 800);
glutCreateWindow("Test 2D Clipping and Viewing");
init();
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutSpecialFunc(special_key);
glutDisplayFunc(display);
glutMainLoop();
return 0;
}