实验四: 直线、多边形裁剪实验
(直线和多边形的裁剪)
1、实验目的和要求
实验目的:
- 理解二维图形裁剪的算法原理;
- 掌握Cohen-SutherLand裁剪算法、Liang-Barsky裁剪算法;
- 了解OpenGL程序实现SH多边形裁剪算法;
- 了解OpenGL程序实现Weiler-Atherton多边形裁剪算法。
实验内容:
- 用一个矩形窗口模拟裁剪多变形,对线段进行Cohen-Sutherland裁减。
- 用一个矩形窗口模拟裁剪多变形,对线段进行Liang-Barsky裁减。
- 用OpenGL程序实现SH多边形裁剪算法
- 用OpenGL程序实现Weiler-Atherton多边形裁剪算法
2、实验设备
PC机、CodeBlocks\VS系列\OpenGL安装包
3、实验内容及原理
实验原理(基本知识)
1) Cohen-Sutherland裁剪算法
又称编码裁剪算法,对每条直线段分三种情况处理:
① 如果点P1, P2完全在裁剪窗口内,则该直线段完全可见,“简取”之;
② 如果点P1, P2都在窗口外,且在窗口的同一外侧,则该直线段完全不可见,“简弃”之;
③ 若不满足“简取”和“简弃”的条件,则直线段可能与窗口相交,此时对直线段按交点进行分段,分段后重复上述处理。
2) Liang-Barsky裁剪算法
从直线的参数方程出发,计算pk、qk和uk。
对于pk=0的情况,,
。若
时,将其代入参数方程式。
对于pk≠0的情况,Liang-Barsky算法将直线段与窗口边界的实交点和虚交点分为两组,下限组以pk<0为特征,表示在该处直线段从裁剪边界延长线的外部延伸到内部;上限组以pk>0为特征,表示在该处直线段从裁剪边界延长线的内部延伸到外部。在有交点的情况下,下限组分布于直线段的起点一侧,上限组则分布于直线段终点一侧,则下限组的最大值和上限值的最小值就分别对应于直线段在窗口内部分的端点。
3) Sutherland-Hodgeman多边形裁剪算法
又称逐边裁剪算法,其基本思想是将多边形的边界作为一个整体,每次用窗口的一条边界对要裁剪的多边形进行裁剪,体现“分而治之”的思想。每次裁剪时把落在窗口外部区域的图形去掉,只保留落在窗口内部区域的图形,并把它作为下一次裁剪的多边形。依次用窗口的4条边界对多边形进行裁剪,则原始多边形即被裁剪完毕。
4) Weiler-Atherton多边形裁剪算法
双边裁剪算法,可用于任意凸的和凹的多边形裁剪。
假定按顺时针方向处理顶点,且将用户多边形定义为Ps,窗口矩形为Pw。算法从Ps的任一点出发,跟踪检测P的每一条边,当Ps与Pw相交时(实交点),按如下规则处理:
① 若是由窗口外进入窗口内,则输出可见直线段,转③。
② 若是由窗口内到窗口外,则从当前交点开始,沿窗口边界顺时针检测Pw的边,即用窗口的有效边界去裁剪Ps的边,找到Ps与Pw最靠近当前交点的另一交点。输出可见直线段和由当前交点到另一交点之间窗口边界上的线段,然后返回处理的当前交点。
③ 沿着Ps处理各条边,直到处理完Ps的每一条边,回到起点。
4、实验源程序代码、运行结果
4.1 Cohen-Sutherland裁剪算法
4.1.1源程序代码
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <vector>
#include <string>
using namespace std;
string getCode(int x, int y, vector<double> &w) {
int yt = w[0], yb = w[1], xl = w[2], xr = w[3];
string ans = "0000";
if (y > yt) { // top
ans[0] = '1';
}
if (y < yb) { // bottom
ans[1] = '1';
}
if (x > xr) { // right
ans[2] = '1';
}
if (x < xl) { // left
ans[3] = '1';
}
return ans;
}
void drawLine(vector<double> p1, vector<double> p2) {
glColor3f(0.0f, 0.0f, 0.0f);
glLineWidth(4);
glBegin(GL_LINES);
glVertex2d(p1[0], p1[1]);
glVertex2d(p2[0], p2[1]);
glEnd();
}
int XOR(string code1, string code2) { // 按位或
for (int i = 0; i < 4; i++) {
if (code1[i] == '1' || code2[i] == '1') {
return 1;
}
}
return 0;
}
int BAND(string code1, string code2) { // 按位与
for (int i = 0; i < 4; i++) {
if (!(code1[i] == '1' && code2[i] == '1')) {
return 0;
}
}
return 1;
}
void CS_Cropping(vector<double> p1, vector<double> p2, vector<double> w) {
double yt = w[0], yb = w[1], xl = w[2], xr = w[3];
vector<double> s(2);
string code1 = getCode(p1[0], p1[1], w), code2 = getCode(p2[0], p2[1], w);
// cout << code1 << ' ' << code2 << endl;
while (XOR(code1, code2) != 0) {
// cout << code1 << ' ' << code2 << endl;
if (BAND(code1, code2) != 0) { // 简弃之
return;
}
if (code1 == "0000") {
swap(code1, code2);
swap(p1, p2);
}
int pos = -1; // 0:top, 1:bottom, 2:right, 3:left
for (int i = 3; i >= 0; i--) {
if (code1[i] == '1') {
pos = i;
break;
}
}
s = vector<double>(2); // s(x, y)
if (pos < 2) { // top || bottom
if (pos == 0) { // top
s[1] = yt;
} else { // bottom
s[1] = yb;
}
double k = (p2[1] - p1[1]) / (p2[0] - p1[0]);
// double k = (y2 - y1) / (x2 - x1);
s[0] = p1[0] + (s[1] - p1[1]) / k;
// s[0] = x1 + (s[1] - y1) / k;
} else { // right || left
if (pos == 2) {
s[0] = xr;
} else {
s[0] = xl;
}
double k = (p2[1] - p1[1]) / (p2[0] - p1[0]);
// double k = (y2 - y1) / (x2 - x1);
s[1] = p1[1] + k * (s[0] - p1[0]);
// s[1] = y1 + k * (s[0] - x1);
}
swap(p1, s);
code1 = getCode(p1[0], p1[1], w), code2 = getCode(p2[0], p2[1], w);
}
if (XOR(code1, code2) == 0) { // 简取之
// drawLine
drawLine(p1, p2);
// cout << "completed successfully" << endl;
}
}
void resize(int w, int h) {
if (h == 0) {
h = 1;
}
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h) {
glOrtho(-200.0f, 200.0f, -200.0f, 200.f * h / w, 1.0f, -1.0f);
} else {
glOrtho(-200.0f, 200.0f * w / h, -200.0f, 200.0f, 1.0f, -1.0f);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void display() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3d(1, 0, 0);
// 用 w 来表示裁剪窗口
vector<double> p1(2), p2(2), w(4);
p1[0] = -56.0f, p2[0] = 76.0f;
p1[1] = 10.0f, p2[1] = -50.0f;
// yt = w[0], yb = w[1], xl = w[2], xr = w[3]
w[0] = 50.0f, w[1] = -50.0f, w[2] = -50.0f, w[3] = 50.0f;
// 绘制窗口
glColor3f(1.0f, 0.0f, 0.0f);
glLineWidth(2);
glBegin(GL_LINE_LOOP);
glVertex2d(w[0], w[1]);
glVertex2d(w[1], w[2]);
glVertex2d(w[2], w[3]);
glVertex2d(w[0], w[3]);
glEnd();
// 绘制原始的直线
glColor3f(0, 0, 1);
glLineWidth(3);
glBegin(GL_LINES);
glVertex2d(p1[0], p1[1]);
glVertex2d(p2[0], p2[1]);
glEnd();
// 裁剪
CS_Cropping(p1, p2, w);
glutSwapBuffers();
}
int main(int argc, char *argv[]) {
glutInit(&argc, argv);
glutInitWindowSize(400, 400);
glutInitWindowPosition(10,10);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow("CS Cropping");
glutReshapeFunc(resize);
glutDisplayFunc(display);
glClearColor(1,1,1,1);
glutMainLoop();
return 0;
}
4.1.2运行结果
图中,黑色加粗部分为裁剪后的,蓝色部分是被裁剪去的,红色矩形表示模拟的裁剪窗口。
4.2 Liang-Barsky算法
4.2.1源代码
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cmath>
#include <vector>
#include <iostream>
using namespace std;
int LBLineClipTest(float p, float q, float &umax, float &umin) {
float r = 0.0f;
if (p < 0.0) {
r = q / p;
if (r > umin) {
return 0;
} else if (r > umax) {
umax = r;
}
} else if (p > 0.0) {
r = q / p;
if (r < umax) {
return 0;
} else if (r < umin) {
umin = r;
}
} else if (q < 0.0) {
return 0;
}
return 1;
}
// drawPixel
void drawPixel(int x, int y) {
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
// BresenhamLine
void BresenhamLine(vector<int> p0, vector<int> p1) {
int x0 = p0[0], y0 = p0[1],
x1 = p1[0], y1 = p1[1];
// 两种特殊情况: x0 == x1 || y0 == y1
if (x0 == x1) {
if (y0 > y1) {
swap(y0, y1);
}
for (int i = y0; i <= y1; i++) {
drawPixel(x0, i);
}
return ;
} else if (y0 == y1) {
if (x0 > x1) {
swap(x0, x1);
}
for (int i = x0; i <= x1; i++) {
drawPixel(i, y0);
}
return ;
}
int kRev = (y1 - y0) / (x1 - x0);
bool isKGT1 = false; // 1 : K is greater than 1
bool isKNeg = (y1 - y0) * (x1 - x0) >= 0 ? false : true; // 1 : K is negative
if (abs(kRev) >= 1) { // |k| > 1
swap(x0, y0);
swap(x1, y1);
isKGT1 = true;
}
if (isKGT1) {
if (isKNeg) {
if (y0 < y1) {
swap(y0, y1);
swap(x0, x1);
}
} else {
if (y0 > y1) {
swap(y0, y1);
swap(x0, x1);
}
}
} else {
if (x0 > x1) {
swap(y0, y1);
swap(x0, x1);
}
}
int x = x0, y = y0;
int dx = x1 - x0, dy = y1 - y0;
if (isKNeg) {
dy = -dy;
}
int e = -dx;
while (x <= x1) {
if (isKGT1) {
drawPixel(y, x);
} else {
drawPixel(x, y);
}
x++;
e += 2 * dy;
if (e > 0) {
if (isKNeg) {
y--;
} else {
y++;
}
e -= 2 * dx;
}
}
}
void LB_Cropping(float xwl, float xwr, float ywb, float ywt, float x1, float y1, float x2, float y2) {
float deltax = x2 - x1,
deltay = y2 - y1,
umax = 0.0f,
umin = 1.0f;
if (LBLineClipTest(-deltax, x1 - xwl, umax, umin)) { // 左边界
if (LBLineClipTest(deltax, xwr - x1, umax, umin)) { // 右边界
if (LBLineClipTest(-deltay, y1 - ywb, umax, umin)) { // 下边界
int tempx1 = x1, tempy1 = y1; // 保存临时值
if (LBLineClipTest(deltay, ywt - y1, umax, umin)) { // 上边界
x1 = int(tempx1 + umax * deltax + 0.5);
y1 = int(tempy1 + umax * deltay + 0.5);
x2 = int(tempx1 + umin * deltax + 0.5);
y2 = int(tempy1 + umin * deltay + 0.5);
}
vector<int> p1(2), p2(2);
p1[0] = int(x1), p1[1] = int(y1);
p2[0] = int(x2), p2[1] = int(y2);
BresenhamLine(p1, p2);
}
}
}
}
void resize(int w, int h) {
if (h == 0) {
h = 1;
}
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h) {
glOrtho(0.0f, 400.0f, 0.0f, 400.f * h / w, 1.0f, -1.0f);
} else {
glOrtho(0.0f, 400.0f * w / h, 0.0f, 400.0f, 1.0f, -1.0f);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void display() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3d(1, 0, 0);
// 用 w 来表示裁剪窗口
vector<float> p1(2), p2(2), w(4);
p1[0] = 1.0f, p1[1] = 1.0f;
p2[0] = 200.0f, p2[1] = 300.0f;
// yt = w[0], yb = w[1], xl = w[2], xr = w[3]
w[0] = 200, w[1] = 10, w[2] = 10.0f, w[3] = 200.0f;
// 绘制窗口
glColor3f(1.0f, 0.0f, 0.0f);
glLineWidth(2);
glBegin(GL_LINE_LOOP);
glVertex2d(w[0], w[1]);
glVertex2d(w[1], w[2]);
glVertex2d(w[2], w[3]);
glVertex2d(w[0], w[3]);
glEnd();
// 绘制原始的直线
glColor3f(0.8, 0.8, 0.8);
glLineWidth(2);
vector<int> pi1(2), pi2(2);
pi1[0] = int(p1[0]), pi1[1] = int(p1[1]);
pi2[0] = int(p2[0]), pi2[1] = int(p2[1]);
BresenhamLine(pi1, pi2);
// 裁剪
glColor3f(1, 0, 0);
glLineWidth(5);
LB_Cropping(10, 200, 10, 200, p1[0], p1[1], p2[0], p2[1]);
glutSwapBuffers();
}
int main(int argc, char *argv[]) {
glutInit(&argc, argv);
glutInitWindowSize(400, 400);
glutInitWindowPosition(10,10);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow("LB Cropping");
glutReshapeFunc(resize);
glutDisplayFunc(display);
glClearColor(1,1,1,1);
glutMainLoop();
return 0;
}
4.2.2运行结果
浅灰色部分表示被裁剪去的部分,红色部分直线表示裁剪后的结果。红色矩形表示模拟的裁剪窗口。
4.3 Sutherland-Hodgeman多边形裁剪算法
4.3.1源代码
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <cmath>
#include <vector>
#include <iostream>
using namespace std;
struct Point {
double x, y;
};
void resize(int w, int h) {
if (h == 0) {
h = 1;
}
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h) {
glOrtho(0.0f, 400.0f, 0.0f, 400.f * h / w, 1.0f, -1.0f);
} else {
glOrtho(0.0f, 400.0f * w / h, 0.0f, 400.0f, 1.0f, -1.0f);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
/**
*
* @param A
* @param B
* @param type 0: l, 1 : b, 2: r, 3: t
* @return
*/
Point clip(Point A, Point B, int type, int val) {
double deltaX = A.x - B.x, deltaY = A.y - B.y;
// y = kx + b;
/*
* k = deltay / deltax;
* b = ya - k * xa
* x = (y - b) / k;
*/
double k = deltaY / deltaX;
double b = A.y - k * A.x;
Point ans;
if (type == 0 || type == 2) {
ans.x = val;
if (deltaY == 0) {
ans.y = A.y;
} else {
ans.y = k * val + b;
}
} else if (type == 1 || type == 3) {
ans.y = val;
if (deltaX == 0) {
ans.x = A.x;
} else {
ans.x = (ans.y - b) / k;
}
}
return ans;
}
void SHPolygonClip(vector<Point> &input, int x, int y, int width, int height) {
// left, _x = x
vector<Point> afterLeft;
for (unsigned int i = 0; i < input.size(); i++) {
if (i == input.size() - 1) {
Point A = input[i], B = input[0];
if (A.x < x && B.x < x) {
continue ;
} else if (A.x >= x && B.x >= x) {
afterLeft.push_back(B);
} else {
Point C = clip(A, B, 0, x);
if (B.x < x) {
afterLeft.push_back(C);
} else if (A.x < x) {
afterLeft.push_back(C);
afterLeft.push_back(B);
}
}
} else {
Point A = input[i], B = input[i + 1];
if (A.x < x && B.x < x) {
continue ;
} else if (A.x >= x && B.x >= x) {
afterLeft.push_back(B);
} else {
Point C = clip(A, B, 0, x);
if (B.x < x) {
afterLeft.push_back(C);
} else if (A.x < x) {
afterLeft.push_back(C);
afterLeft.push_back(B);
}
}
}
}
// bottom, _y = y
vector<Point> afterBottom;
for (unsigned int i = 0; i < afterLeft.size(); i++) {
if (i == afterLeft.size() - 1) {
Point A = afterLeft[i], B = afterLeft[0];
if (A.y < y && B.y < y) {
continue ;
} else if (A.y >= y && B.y >= y) {
afterBottom.push_back(B);
} else {
Point C = clip(A, B, 1, y);
if (B.y < y) {
afterBottom.push_back(C);
} else if (A.y < y) {
afterBottom.push_back(C);
afterBottom.push_back(B);
}
}
} else {
Point A = afterLeft[i], B = afterLeft[i + 1];
if (A.y < y && B.y < y) {
continue ;
} else if (A.y >= y && B.y >= y) {
afterBottom.push_back(B);
} else {
Point C = clip(A, B, 1, y);
if (B.y < y) {
afterBottom.push_back(C);
} else if (A.y < y) {
afterBottom.push_back(C);
afterBottom.push_back(B);
}
}
}
}
// right, _x = x + width
x += width;
vector<Point> afterRight;
for (unsigned int i = 0; i < afterBottom.size(); i++) {
if (i == afterBottom.size() - 1) {
Point A = afterBottom[i], B = afterBottom[0];
if (A.x > x && B.x > x) {
continue;
} else if (A.x <= x && B.x <= x) {
afterRight.push_back(B);
} else {
Point C = clip(A, B, 2, x);
if (B.x > x) {
afterRight.push_back(C);
} else if (A.x > x) {
afterRight.push_back(C);
afterRight.push_back(B);
}
}
} else {
Point A = afterBottom[i], B = afterBottom[i + 1];
if (A.x > x && B.x > x) {
continue;
} else if (A.x <= x && B.x <= x) {
afterRight.push_back(B);
} else {
Point C = clip(A, B, 2, x);
if (B.x > x) {
afterRight.push_back(C);
} else if (A.x > x) {
afterRight.push_back(C);
afterRight.push_back(B);
}
}
}
}
// top, _y = y + height
y += height;
vector<Point> afterTop;
for (unsigned int i = 0; i < afterRight.size(); i++) {
if (i == afterRight.size() - 1) {
Point A = afterRight[i], B = afterRight[0];
if (A.y > y && B.y > y) {
continue;
} else if (A.y <= y && B.y <= y) {
afterTop.push_back(B);
} else {
Point C = clip(A, B, 3, y);
if (B.y > y) {
afterTop.push_back(C);
} else if (A.y > y) {
afterTop.push_back(C);
afterTop.push_back(B);
}
}
} else {
Point A = afterRight[i], B = afterRight[i + 1];
if (A.y > y && B.y > y) {
continue;
} else if (A.y <= y && B.y <= y) {
afterTop.push_back(B);
} else {
Point C = clip(A, B, 3, y);
if (B.y > y) {
afterTop.push_back(C);
} else if (A.y > y) {
afterTop.push_back(C);
afterTop.push_back(B);
}
}
}
}
// draw
glBegin(GL_POLYGON);
for (unsigned int i = 0; i < afterTop.size(); i++) {
glVertex2d(afterTop[i].x, afterTop[i].y);
}
glEnd();
}
void display() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3d(1, 0, 0);
vector<Point> points;
Point p[8];
// 0-200 0-200
p[0].x = 105, p[0].y = 105;
p[1].x = 170, p[1].y = 20;
p[2].x = 320, p[2].y = 170;
p[3].x = 350, p[3].y = 310;
p[4].x = 170, p[4].y = 350;
// p[5].x = 350, p[5].y = 310;
// p[6].x = 350, p[6].y = 310;
// borders
glColor3f(0, 0, 0);
glBegin(GL_LINE_LOOP);
glVertex2i(40, 40);
glVertex2i(40, 300);
glVertex2i(300, 300);
glVertex2i(300, 40);
glEnd();
// origin
glColor3f(0.9, 0.9, 0.9);
glBegin(GL_POLYGON);
for (int i = 0; i < 5; i++) {
glVertex2d(p[i].x, p[i].y);
}
glEnd();
for (int i = 0; i < 5; i++) {
points.push_back(p[i]);
}
glColor3f(0, 0, 1);
SHPolygonClip(points, 40, 40, 260, 260);
glutSwapBuffers();
}
int main(int argc, char *argv[]) {
glutInit(&argc, argv);
glutInitWindowSize(400, 400);
glutInitWindowPosition(10, 10);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutCreateWindow("SH Polygon Clip");
glutReshapeFunc(resize);
glutDisplayFunc(display);
glClearColor(1,1,1,1);
glutMainLoop();
return 0;
}
4.3.2运行结果
浅灰色部分表示被裁剪去的部分,蓝色部分表示被保留的部分,黑色矩形是模拟的裁剪窗口。