实验内容:

  • 实现 LiangLiang-BarskyBarsky 裁剪算法

画线过程:

  由于 LiangLiang-BarskyBarsky 算法是矩形窗口对于直线段的裁剪,考虑到鼠标的交互性,需要添加画线程序。

  因此调用了 STL 中的 vector 来保存所有直线, 采用了 BresenhamBresenham 算法来画出直线。

struct BASE{
    int x0,y0,x1,y1;
};
vector<BASE> Vline,VRectangle,Vline1;
//Vline —— 保存已经画完的直线, Vline1 —— 保存当前画的直线, VRectangle —— 保存窗口坐标

画矩形过程:

  因为此裁剪算法是用矩形窗口对直线进行裁剪,因此矩形的构造不可避免。 此处采用了两点确定一个矩形的原则,用矩形四个角上的两个端点来确定这个 矩形。

  这里会出现一个小问题,就是四个角上的两个端点不一定是左下角和右下 角,因此我们需要先对 x、y 坐标求一个最大和最小值,以此确定左下角和右下 角,便于绘制这个图形。然后再调用 Bresenham 算法画线即可。

if(VRectangle.size()){
        int x0 = VRectangle[0].x0, y0 = VRectangle[0].y0, x1 = VRectangle[0].x1, y1 = VRectangle[0].y1;
        int xlow = min(x0,x1), ylow = min(y0,y1), xh = max(x0,x1), yh = max(y0,y1);
        DrawLine(xlow, ylow, xlow, yh);
        DrawLine(xlow, ylow, xh, ylow);
        DrawLine(xlow, yh, xh, yh);
        DrawLine(xh, ylow, xh, yh);
        for(int i = 0; i < Vline.size(); i++){
            LiangBarsky(Vline[i].x0,Vline[i].y0,Vline[i].x1,Vline[i].y1,xlow,xh,ylow,yh);
        }
    }

裁剪算法:

裁剪算法即实现了 LiangLiang-BarskyBarsky 的算法原理。基本步骤如下。

  • 输入 (x1,y1)(x2,y2)wxlwxrwybwyt(x_1,y_1)、(x_2,y_2)、wxl、wxr、wyb、wyt

  • Δx=0\Delta x = 0,则 p1=p2=0p_1=p_2=0,此时进一步判断 q1&lt;0  q2&lt;0q_1 &lt; 0 \ ||\ q_2 &lt; 0,则直线段不在窗口内,转⑦结束。否则,满足 0q10\leq q_1 && 0q20\leq q_2,进一步计算 umaxu_{max}uminu_{min}

  • Δy=0\Delta y=0,则 p3=p4=0p_3=p_4=0,此时进一步判断是否满足 q3&lt;0  q4&lt;0q_3 &lt; 0 \ ||\ q_4 &lt; 0,若满足则该直线段不在窗口内,转⑦结束。否则,0q30\leq q_3 && 0q40\leq q_4,进一步计算 umaxu_maxuminu_min

  • 若上述两条均不满足,则 pk≠0 (k=1,2,3,4)p_k =\not 0 \ (k = 1,2,3,4),则计算 umaxu_{max}uminu_{min}。 求得 umaxu_{max}uminu_{min} 后,进行判断:若 umaxu_{max} > uminu_{min},则直线段在窗口外, 转⑦。若 umaxuminu_{max} \leq u_{min},则得到 xxyy 坐标。

  • 画出直线段。

  • 算法结束。 代码实现过程中,该函数的参数为直线的两点坐标,窗口的左右上下边界, 然后在函数中直接判断此直线在窗外还是窗内,然后直接画出直线。
    【OpenGL 实验二】Liang-Barsky 裁剪算法

void LiangBarsky(int x1, int y1, int x2, int y2, int xleft, int xright, int ybottom, int ytop){
    int p[5] = {0,x1-x2,x2-x1,y1-y2,y2-y1},
        q[5] = {0,x1-xleft,xright-x1,y1-ybottom,ytop-y1}, L = 1, R = 4;
    db u[5], umin = 1, umax = 0;
    if(p[1] == 0 && p[3] == 0) return;
    if(p[1] == 0){
        if(q[1] < 0 || q[2] < 0) return;
        else {L = 3; R = 4;}
    }
    else if(p[3] == 0){
        if(q[3] < 0 || q[4] < 0) return;
        else {L = 1; R = 2;}
    }
    for(int i = L; i <= R; i++){
        u[i] = (db)q[i]/(db)p[i];
        if(p[i] < 0) umax = max(umax,u[i]);
        else umin = min(umin,u[i]);
    }
    if(sign(umax-umin) == 1) return;
    db xans1 = (db)x1+umin*(db)(x2-x1), yans1 = (db)y1+umin*(db)(y2-y1), xans2 = (db)x1+umax*(db)(x2-x1), yans2 = (db)y1+umax*(db)(y2-y1);
    DrawLine((int)xans1, (int)yans1, (int)xans2, (int)yans2);
}

功能演示:

  • 确定窗口
    选择画矩形按钮确定窗口。如果直接画线会默认无窗口。
    【OpenGL 实验二】Liang-Barsky 裁剪算法
    鼠标拖拽即可确定窗口矩形。
    【OpenGL 实验二】Liang-Barsky 裁剪算法
  • 画线
    选择画线按钮进行画线裁剪。
    【OpenGL 实验二】Liang-Barsky 裁剪算法
    鼠标拖拽即可画线,刚画好的直线不会直接裁剪,当松开鼠标画下一条直线时才会认为上一条直线已经画完,才开始裁剪。
    【OpenGL 实验二】Liang-Barsky 裁剪算法
    【OpenGL 实验二】Liang-Barsky 裁剪算法
  • 在画矩形部分重新确定窗口
    由于画出的直线都会被保存,因此可以重新确定窗口,每条直线都会被再次裁 剪后输出。拖拽鼠标画出窗口即可看到之前每条直线被裁剪之后显示的图形。
    【OpenGL 实验二】Liang-Barsky 裁剪算法
    【OpenGL 实验二】Liang-Barsky 裁剪算法
    【OpenGL 实验二】Liang-Barsky 裁剪算法

细节:

  • 回调函数中每次刷新屏幕,每次重新画线、画矩形。在矩形窗口存在的时候 才会画线,矩形窗口不存在的时候不会显示直线。
  • 由于 Liang-Barsky 算法中涉及到了浮点数比较,因此手写了比较函数,将 精度误差限制在了 1e-7。
typedef double db;
const db EPS = 1e-7;
inline int sign(db a) {return a < -EPS ? -1 : a > EPS; } //返回-1表示a < 0, 1表示a > 0, 0表示a = 0

完整代码:

#include <GLUT/GLUT.h>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <vector>
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

int WinWidth = 1024, Winheight = 720; //窗口宽高
int X0,Y0,X1,Y1,Xvalue; //起始坐标与终止坐标
void init();
void Mymenu(int);
void DrawLine(int,int,int,int);
void BresenhamLine(int,int,int,int);
void LiangBarsky(int,int,int,int,int,int,int,int);

typedef double db;
const db EPS = 1e-7;
inline int sign(db a) {return a < -EPS ? -1 : a > EPS; } //返回-1表示a < 0, 1表示a > 0, 0表示a = 0

void swap(GLint& a,GLint &b) {GLint t = a; a = b; b = t;}
void setPixel(GLint x,GLint y){
    glBegin(GL_POINTS); //把每个顶点作为一个点来处理
    glVertex2i(x,y); //int坐标画点
    glEnd();
}
struct BASE{
    int x0,y0,x1,y1;
};
vector<BASE> Vline,VRectangle,Vline1;
//Vline —— 保存已经画完的直线, Vline1 —— 保存当前画的直线, VRectangle —— 保存窗口坐标

void ReDraw(){
//    printf("Line.size(): %d\n",(int)Vline.size());
    if(VRectangle.size()){
        int x0 = VRectangle[0].x0, y0 = VRectangle[0].y0, x1 = VRectangle[0].x1, y1 = VRectangle[0].y1;
        int xlow = min(x0,x1), ylow = min(y0,y1), xh = max(x0,x1), yh = max(y0,y1);
        DrawLine(xlow, ylow, xlow, yh);
        DrawLine(xlow, ylow, xh, ylow);
        DrawLine(xlow, yh, xh, yh);
        DrawLine(xh, ylow, xh, yh);
        for(int i = 0; i < Vline.size(); i++){
            LiangBarsky(Vline[i].x0,Vline[i].y0,Vline[i].x1,Vline[i].y1,xlow,xh,ylow,yh);
        }
    }
    if(Vline1.size()) DrawLine(Vline1[0].x0, Vline1[0].y0, Vline1[0].x1, Vline1[0].y1);
}

void display(){
    glClear (GL_COLOR_BUFFER_BIT);
    glRectf (-1.0, -1.0, 1.0, 1.0);
    ReDraw();
    glutSwapBuffers ();
}

void update(){
    if(Xvalue == 1){ //Line
        Vline1[0].x1 = X1; Vline1[0].y1 = Y1;
        if(Vline.size() == 0 || (Vline[Vline.size()-1].x0 != X0 && Vline[Vline.size()-1].y0 != Y0))
            Vline.push_back({X0,Y0,X1,Y1});
        else{
            Vline[Vline.size()-1].x1 = X1;
            Vline[Vline.size()-1].y1 = Y1;
        }
    }
    else if(Xvalue == 2){
        VRectangle[0].x1 = X1; VRectangle[0].y1 = Y1;
    }
    display();
}

void Dragmouse(int x,int y){ //鼠标拖拽
    X1 = x; Y1 = y; update();
}

void Mymouse(int button, int state, int x, int y){
    if(button == GLUT_LEFT_BUTTON && state == GLUT_UP){
        X1 = x; Y1 = y;
        update();
    }
    else if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN){
        X0 = x; Y0 = y;
        if(Xvalue == 1){
            Vline1.clear();
            Vline1.push_back((BASE){X0,Y0,X0,Y0});
        }
        else if(Xvalue == 2){
            Vline1.clear();
            VRectangle.clear();
            VRectangle.push_back((BASE){X0,Y0,X0,Y0});
        }
    }
}

int main(int argc, char** argv){
    glutInit(&argc, argv); //初始化
    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); //指定单缓存窗口与RGB颜色模式的窗口
    glutInitWindowPosition(500, 300); //设置初始窗口的位置,(0,0)为屏幕左上角位置
    glutInitWindowSize(WinWidth, Winheight); //设置窗口宽度与高度,单位为像素
    glutCreateWindow("图形学实验二"); //创建窗口
    init();
    
    glutDisplayFunc(display);//注册显示回调函数
    glutCreateMenu(Mymenu);//注册菜单回调函数
    glutAddMenuEntry("DrawLine",1);//添加菜单项
    glutAddMenuEntry("DrawRectangle",2);
    glutAddMenuEntry("ClearScreen",3);
    glutAddMenuEntry("Exit",4);
    glutAttachMenu(GLUT_RIGHT_BUTTON);//把当前菜单注册到指定的鼠标键
    glutMainLoop();
}

void init(){
    glClearColor(0,0,0,1); //(red green blue alpha) alpha表示混合因子
    glClear(GL_COLOR_BUFFER_BIT); //将屏幕所有像素点还原为"底色"
    glPointSize(3.0f); //指定栅格化点的直径
    glMatrixMode(GL_PROJECTION); //声明接下来要进行的操作,GL_PROJECTION 投影, GL_MODELVIEW 模型视图, GL_TEXTURE 纹理
    glLoadIdentity(); //加载一个单位矩阵
    gluOrtho2D(0,WinWidth,Winheight,0); //定义裁剪面
    glColor3f(255,215,0); //设置画点颜色 R-G-B
    glutMouseFunc(Mymouse);
    glutMotionFunc(Dragmouse);
}

void DrawLine(int x1,int y1,int x2,int y2){ //画直线函数
    BresenhamLine(x1,y1,x2,y2);
}

void Mymenu(int value){
    Xvalue = value;
    if (value == 3){
        glClear(GL_COLOR_BUFFER_BIT);
        glutSwapBuffers();
        Vline1.clear();
        Vline.clear();
        VRectangle.clear();
    }
    else if (value == 4){
        exit(0);
    }
}

void BresenhamLine(int x1,int y1,int x2,int y2){
    int x = x1, y = y1, dx = abs(x2-x1), dy = abs(y2-y1), s1 = 1, s2 = 1, e, flag = 0;
    if(x1 >= x2) s1 = -1;
    if(y1 >= y2) s2 = -1;
    if(dy > dx) {swap(dx,dy); flag = 1;}
    e = -dx;
    int DX = 2*dx, DY = 2*dy;
    for(int i = 1; i <= dx; i++){
        setPixel(x, y);
        if(e >= 0){
            if(!flag) y += s2;
            else x += s1;
            e = e-DX;
        }
        if(!flag) x += s1;
        else y += s2;
        e = e+DY;
    }
}

void LiangBarsky(int x1, int y1, int x2, int y2, int xleft, int xright, int ybottom, int ytop){
    int p[5] = {0,x1-x2,x2-x1,y1-y2,y2-y1},
        q[5] = {0,x1-xleft,xright-x1,y1-ybottom,ytop-y1}, L = 1, R = 4;
    db u[5], umin = 1, umax = 0;
    if(p[1] == 0 && p[3] == 0) return;
    if(p[1] == 0){
        if(q[1] < 0 || q[2] < 0) return;
        else {L = 3; R = 4;}
    }
    else if(p[3] == 0){
        if(q[3] < 0 || q[4] < 0) return;
        else {L = 1; R = 2;}
    }
    for(int i = L; i <= R; i++){
        u[i] = (db)q[i]/(db)p[i];
        if(p[i] < 0) umax = max(umax,u[i]);
        else umin = min(umin,u[i]);
    }
    if(sign(umax-umin) == 1) return;
    db xans1 = (db)x1+umin*(db)(x2-x1), yans1 = (db)y1+umin*(db)(y2-y1), xans2 = (db)x1+umax*(db)(x2-x1), yans2 = (db)y1+umax*(db)(y2-y1);
    DrawLine((int)xans1, (int)yans1, (int)xans2, (int)yans2);
}

相关文章: