一、目的
1. 了解直线裁剪的基本原理和常用方法。
2. 掌握中点分割直线段裁剪算法的基本原理和步骤。
3. 使用C++、OpenGL编程实现如下内容:输入直线段的起始点和终止点坐标位置、用四边形模拟显示器的边界,利用中点分割直线段裁剪算法计算直线在模拟显示器内部的裁剪结果。
二、实验设备
编程语言:C++、OpenGL
实验环境:已安装相关编程环境的计算机1台。
三、算法原理
中点分割直线段裁剪算法对Cohen-Sutherland直线裁剪算法的第3种情况做了改进,原理是简单地把起点为P0,终点为P1的直线段等分为两段直线P0P和PP1(P为直线段中点),对每一段直线重复“简取”和“简弃”的处理,对于不能处理的直线段再继续等分下去线,直至每一段直线完全能够被“简取”或“简弃”,也就是说直至每段直线完全位于窗口之内或完全位于窗口之外,就完成了直线段的裁剪工作。
算法步骤
(1)以P1出发,检测点P2是否在窗口内,若是,即是所求点;若不转(2)
(2)检测点P1P2是否在同一外侧,若是,则不在窗口内;返回;不转(3)
(3)求出该直线段的中点P3。检测P3与P1P2的关系:
若中点P3在窗口内,用P3代替P1;重复步骤(3);
若中点P3不在窗口内,判断P3与P2的关系,
若P3、P2在同侧外面,则用P3代替P2,即丢掉P2P3段;
若不在同侧外面,则用P3代替P1,重复步骤(3)。直到满足误差条件,则该中点就是该线段落距离P1最远的可见点。
(4)重复步骤(2)-(3),求出另一个端点P2的最远可见点坐标。
四、算法实现及结果分析
#include <GL/glut.h>
#include <cstdio>
#include<iostream>
using namespace std;
#define LEFT 1//左边界
#define RIGHT 2//右边界
#define BOTTOM 4//下边界
#define TOP 8//上边界
/*int x1 = 150, y1 = 50, x2 = 50, y2 = 250, XL = 100, XR = 300, YB = 100, YT = 200; //(x1,y1)、(x2,y2)为直线段的端点,XL为左边界,XR为右边界,YB为下边界,YT为上边界
int x1_init = 150, y1_init = 50, x2_init = 50, y2_init = 250; //将直线段端点备份,以便画出裁剪前的直线段*/
double a1, b1, a2, b2, A1, B1, A2, B2, XL = 100, XR = 300, YB = 100, YT = 200;
double x1_init, y1_init, x2_init, y2_init;
int encode(double x, double y)//按位或操作找到该点的空间区域
{
int c = 0;
if (x < XL) c |= LEFT;
if (x > XR) c |= RIGHT;
if (y < YB) c |= BOTTOM;
if (y > YT) c |= TOP;
return c;
}
int border(double mx,double my) {
int c = 0;
if (abs(mx - XL) < 1) {
c |= LEFT;
}
if (abs(mx - XR) < 1) {
c |= RIGHT;
}
if (abs(my - YB) < 1) {
c |= BOTTOM;
}
if (abs(my - YT) < 1) {
c |= TOP;
}
return c;
}
bool pm(double mx, double my) {
if (abs(mx - XL) < 1 || abs(mx - XR) < 1 || abs(my - YB) < 1 || abs(my - YT) < 1) {
return false;
}
else {
return true;
}
}
bool Mid_LineClip1() {
double mx1, my1;
int code1, code2, code;
A2 = a2, B2 = b2;//确保(a2,b2)不被更改
code1 = encode(a1, b1);//得到该点的空间区域
code2 = encode(A2, B2);
mx1 = (a1 + A2) / 2;
my1 = (b1 + B2) / 2;
code = encode(mx1, my1);
if (code1 == 0){
A1 = a1, B1 = b1;
return true;
}
else if((code1&code2)!=0){//在窗口外同一侧
A1 = a1, B1 = b1;
return false;
}
else if ((code1 & border(mx1, my1)) != 0) {//中点在边界上,若靠近点1一侧不在经过窗口
a1 = mx1;
b1 = my1;
}
else{
do{
mx1 = (a1 + A2) / 2;
my1 = (b1 + B2) / 2;
code1 = encode(a1, b1);//得到该点的空间区域
code2 = encode(A2, B2);
code = encode(mx1, my1);
if (code == 0) {//中点在窗口内就更新点2(备份)的位置
A2 = mx1;
B2 = my1;
code2 = encode(A2, B2);
}
else {
if ((code & code1) != 0) {//中点在窗口外和点1同一侧就更新点1的位置
a1 = mx1;
b1 = my1;
code1 = encode(a1, b1);
}
else {//中点在窗口外和点2在同一侧就更新点2的位置
A2 = mx1;
B2 = my1;
code2 = encode(A2, B2);
}
}
} while (pm(a1, b1));
}
/*if (abs(a1 - XL) < 5) {//调整误差
a1 = XL;
}
else if(abs(a1-XR)<5){
a1 = XR;
}
if (abs(b1 - YB) < 5) {
b1 = YB;
}
else if(abs(b1 - YT) < 5){
b1 = YT;
}*/
A1 = a1, B1 = b1;//保留更新的点1
//cout << "a1 " << a1 << "b1 " << b1 << endl;
//cout << "A2 " << A2 << "B2 " << B2 << endl;
return true;
}
bool Mid_LineClip2() {
double mx2, my2;
int code1, code2, code;
code1 = encode(A1, B1);//得到该点的空间区域
code2 = encode(a2, b2);
mx2 = (A1 + a2) / 2;
my2 = (B1 + b2) / 2;
code = encode(mx2, my2);
if (code2 == 0) {
return true;
}
else if ((code2 & border(mx2, my2)) != 0) {//中点在边界上,若靠近点2一侧不在经过窗口
a2 = mx2;
b2 = my2;
}
else {
do {
mx2 = (A1 + a2) / 2;
my2 = (B1 + b2) / 2;
code1 = encode(A1, B1);//得到该点的空间区域
code2 = encode(a2, b2);
code = encode(mx2, my2);
if (code == 0) {//中点在窗口内就更新点1的位置
A1 = mx2;
B1 = my2;
code1 = encode(A1, B1);
}
else {//中点在窗口外和点2同一侧就更新点2的位置
a2 = mx2;
b2 = my2;
code2 = encode(a2, b2);
}
} while (pm(a2, b2));
}
/*if (abs(a2 - XL) < 5) {//调整误差
a2 = XL;
}
else if (abs(a2 - XR) < 5) {
a2 = XR;
}
if (abs(b1 - YB) < 5) {
b2 = YB;
}
else if (abs(a2 - XR) < 5) {
b2 = YT;
}*/
//cout << "A1 " << A1 << "B1 " << B1 << endl;
//cout << "a2 " << a2 << "b2 " << b2 << endl;
return true;
}
void init() //初始化函数
{
glClearColor(0.0, 0.0, 0.0, 0.0); //设置背景颜色
glMatrixMode(GL_PROJECTION); // 设置投影参数
gluOrtho2D(0.0, 600.0, 0.0, 400.0); // 设置场景的大小
//CS_LineClip(); //执行一次裁剪算法
Mid_LineClip1();
Mid_LineClip2();
}
void mydisplay() //显示函数
{
//绘制方形边界
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 0.0, 0.0);
glPointSize(2);
glBegin(GL_LINE_LOOP);
glVertex2i(XL, YT);
glVertex2i(XL, YB);
glVertex2i(XR, YB);
glVertex2i(XR, YT);
glEnd();
glFlush();
//绘制未裁剪前的线段
glBegin(GL_LINES);
glVertex2i(x1_init, y1_init);
glVertex2i(x2_init, y2_init);
glEnd();
glFlush();
//绘制裁剪后的线段
glColor3f(1.0, 1.0, 0.0);
glBegin(GL_LINES);
glVertex2i(a1, b1);
glVertex2i(a2, b2);
glEnd();
glFlush();
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
glutInitWindowPosition(100, 100);
glutInitWindowSize(400, 400);
glutCreateWindow("Cohen-Sutherland裁剪算法");
/*printf("请输入直线端点");
scanf_s("%d%d%d%d", &a1, &b1, &a2, &b2);*/
cout<<"请输入直线端点"<<endl;
cin >> a1 >> b1 >> a2 >> b2;
x1_init = a1, y1_init = b1, x2_init = a2, y2_init = b2;
init();
glutDisplayFunc(&mydisplay);
glutMainLoop();
return 0;
}
在本次实验中,我第一个遇到的问题就是变量 y1 和 C++ 标准库中的y1()函数冲突。我们可以通过改变量名或者使用c语言的标准去写,最终解决了问题。第二个遇到的问题是精度的问题,如果我们用int去对中点与边界的距离是否小于像素点时,误差会比较大。我们可以通过double去细化精度,或者通过判断该点与边界的位置关系将其位移到边界上,这样就提高了画线的精度。第三个遇到的问题就是情况分类,初次完成的代码只能成功画出某些满足条件的直线,但是当遇到直线中点位于边界的情况,就会产生错误。所以,我添加了一个border()函数用于判断靠近哪个边界。到目前为止,参与测试的直线都能够成功画出。