直线矩形视口裁剪算法 Cohen–Sutherland algorithm

来源:互联网 发布:有钱人的三观 知乎 编辑:程序博客网 时间:2024/06/05 20:41

直线矩形视口裁剪算法 Cohen–Sutherland algorithm

算法参见:
http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm

算法思路

  1. 在二维平面中,将一个矩形的四条边无限延伸,可以将平面分割成九个区域,中心的区域就是矩形本身。
  2. 将每个区域给予不同的编号,为了方便使用,采用4个位来表示一个二维平面的一个区域。前2位为相对于矩形区域的上下的区域,后两位为矩形区域左右的区域。
    能够明显看到左右、上下分别赋值给不同的位为1,这样计算出一个点的区域码后,就可以很方便的使用。
    0101 0100 0110 0001 0000 0010 1001 1000 1010
  3. 一条线段的两个端点的区域码做与预算&如果不为0,说明这个线段不用显示。
  4. 一个线段的两个端点区域码或运算|后如果为0,说明他们都在区域内。
  5. 其他情况,我们需要进行裁剪。裁剪过程可以是一个递归的过程:
    • 首先使一个端点的一个轴的坐标在视口的范围内,另一个坐标随之变化。
    • 判断新得到的点对是否满足3、4中的条件,如果不满足重复上一步,直到满足这个条件。

C++实现(采用OpenGL 4.5绘制)

======

#include <iostream>#include <math.h>#include <stdlib.h>using namespace std;#include "vgl.h"#include "LoadShaders.h"enum VAO_IDs { Triangles, NumVAOs };enum Buffer_IDs { ArrayBuffer, NumBuffers };enum Attrib_IDS { vPosition = 1 };GLuint VAOs[NumVAOs];GLuint Buffers[NumBuffers];const GLuint NumVertices = 3;// 效率测量用开始、结束时间SYSTEMTIME tStart, tEnd;const intOUTSIDE_UP = 0x04,  // 视口上方OUTSIDE_DOWN = 0x08, // 视口下方OUTSIDE_LEFT = 0x01, // 视口左方OUTSIDE_RIGHT = 0x02, // 视口右方OUTSIDE_MID = 0x00, // 视口竖直中部OUTSIDE_CENTER = 0x00; // 视口水平中// 设置视口的大小const GLfloatVIEWPORT_UP = 0.5,VIEWPORT_DOWN = -0.3,VIEWPORT_LEFT = -0.8,VIEWPORT_RIGHT = 0.5;/* * 计算一个点相对于视口的区域码。 * Ref: http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm */int calculateAreaCode(GLfloat x, GLfloat y) {    int code = 0x00;    if (x > VIEWPORT_RIGHT) {        code |= OUTSIDE_RIGHT;    }    else if (x < VIEWPORT_LEFT) {        code |= OUTSIDE_LEFT;    }    else {        code |= OUTSIDE_CENTER;    }    if (y > VIEWPORT_UP) {        code |= OUTSIDE_UP;    }    else if (y < VIEWPORT_DOWN) {        code |= OUTSIDE_DOWN;    }    else {        code |= OUTSIDE_MID;    }    return code;}/** * 对一条线段在一个矩形视口中进行裁剪,并判断其是否需要绘制。 * 如果这个线段完全在视口外,则不进行裁剪,也不进行显示; * 如果这个线段完全在视口内,不进行裁剪,进行显示; * 如果这个线段部分在视口内,则进行裁剪,进行显示。 * 裁剪算法使用Cohen-Sutherland算法。具体参见: * http://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm * * @param x0ptr 端点0的横坐标指针 * @param y0ptr 端点0的纵坐标指针 * @param x1ptr 端点1的横坐标指针 * @param y1ptr 端点1的纵坐标指针 * @return 如果需要显示返回true,反之返回false */bool cohenSutherlandAlgorithm(GLfloat* x0ptr, GLfloat* y0ptr, GLfloat* x1ptr, GLfloat* y1ptr) {    bool accepted = false;  // 表示这个线段是否要进行显示    while (true) {            GLfloat x0 = *x0ptr, y0 = *y0ptr, x1 = *x1ptr, y1 = *y1ptr;        // 计算区域码        int areaCode0 = calculateAreaCode(x0, y0);        int areaCode1 = calculateAreaCode(x1, y1);        // 两个都在视口范围内,不进行裁剪        if (!(areaCode0 | areaCode1)) {            accepted = true;            break;        }        // 两个都不在视口范围内,且处于同一侧,不进行裁剪,丢弃        if (areaCode0 & areaCode1) {            break;        }        // 选取其中在区域外的端点        int areaCodeOut = areaCode0 != 0 ? areaCode0 : areaCode1;        GLfloat newX = 0.0, newY = 0.0;        // 计算交叉点,一次至少使一个轴的坐标是在视口内的        if (areaCodeOut & OUTSIDE_UP) {            newY = VIEWPORT_UP;            // 在这里不会出现y0 == y1的情况            newX = (x0 - x1) / (y0 - y1) * (newY - y0) + x0;        }        else if (areaCodeOut & OUTSIDE_DOWN) {            newY = VIEWPORT_DOWN;            // 在这里不会出现y0 == y1的情况            newX = (x0 - x1) / (y0 - y1) * (newY - y0) + x0;        }        else if (areaCodeOut & OUTSIDE_LEFT) {            newX = VIEWPORT_LEFT;            // 在这里不会出现x0 == x1的情况            newY = (y0 - y1) / (x0 - x1) * (newX - x0) + y0;        }        else if (areaCodeOut & OUTSIDE_LEFT) {            newX = VIEWPORT_RIGHT;            // 在这里不会出现x0 == x1的情况            newY = (y0 - y1) / (x0 - x1) * (newX - x0) + y0;        }        if (areaCodeOut == areaCode0) {            *x0ptr = newX;            *y0ptr = newY;        }        else {            *x1ptr = newX;            *y1ptr = newY;        }    }    return accepted;}void init(void) {    glGenVertexArrays(NumVAOs, VAOs);    glBindVertexArray(VAOs[Triangles]);    GLfloat *points = new GLfloat[4];    // 任意输入4个数字,分别为线段端点的坐标x0, y0, x1, y1    cin >> points[0] >> points[1] >> points[2] >> points[3];    // 用 Cohen-Sutherland Algorithm 对直线进行裁剪。    cohenSutherlandAlgorithm(points, points + 1, points + 2, points + 3);    glGenBuffers(NumBuffers, Buffers);    glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]);    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 4, points, GL_STATIC_DRAW);    delete[] points;    ShaderInfo shaders[] {        { GL_VERTEX_SHADER, "triangles.vert" },        { GL_FRAGMENT_SHADER, "triangles.frag" },        { GL_NONE, NULL }    };    GLuint program = LoadShaders(shaders);    glUseProgram(program);    glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));    glEnableVertexAttribArray(vPosition);    // 设置线段宽度大小为5像素    glLineWidth(50);}void display(void) {    // 效率测量    GetLocalTime(&tStart);//    glViewport(100, 100, 512, 512);    glClear(GL_COLOR_BUFFER_BIT);    glBindVertexArray(VAOs[Triangles]);    glDrawArrays(GL_LINES, 0, 2);    // glFlush();    glFinish();    // 效率测量    GetLocalTime(&tEnd);    cout << "Total Time: " << tEnd.wMilliseconds - tStart.wMilliseconds << endl;}int main(int argc, char** argv) {    glutInit(&argc, argv);    glutInitDisplayMode(GLUT_RGBA);    glutInitWindowSize(512, 512);    glutInitContextVersion(4, 5);    glutInitContextProfile(GLUT_CORE_PROFILE);    glutCreateWindow(argv[0]);    glewExperimental = GL_TRUE;    if (glewInit()) {        cerr << "Unable to initialize GLEW ... exiting" << endl;        exit(EXIT_FAILURE);    }    init();    glutDisplayFunc(display);    glutMainLoop();    return 0;}
0 0