计算机图形学学习记录(五) —— Cohen Sutherland 编码算法 (利用graphic.h实现的版本)

算法描述

首先,是对算法的一个解释描述,引用开源项目中的解释为:

Cohen Sutherland

Cohen-Sutherland algorithm divides a two-dimensional space into 9 regions and then efficiently determines the lines and portions of lines that are inside the given rectangular area.

Cohen-Sutherland 算法将一个二维空间分成九个区域来有效的判别行和部分航是否是在矩形显示区域内。

There are three possible cases for any given line.

将会出现三种线和矩形区域的显示关系
Completely inside the given rectangle :
Bitwise OR of region of two end points of line is 0 (Both points are inside the rectangle)
完全在矩形内:针对两个端点位的或运算结果是0(说明两个端点全在矩形内)。
Completely outside the given rectangle :
Both endpoints share at least one outside region which implies that the line does not cross the visible region. (bitwise AND of endpoints != 0).
完全在矩形外:两个点至少穿过一个完全不同国可见矩形区域的外部位置。(针对两个点位与运算结果不为0)
Partially inside the window :
Both endpoints are in different regions. In this case, the algorithm finds one of the two points that is outside the rectangular region. The intersection of the line from outside point and rectangular window becomes new corner point and the algorithm repeats
部分在可见范围内:两个端点在不同的区域内,在这种情况下,该算法寻找一个或两个在矩形域外的点。然后找到先从外到矩形域内的交点,并将它作为新的端点作为新一轮算法流程中使用。

Pseudo Code:

Step 1 : Assign a region code for two endpoints of given line.
1:计算两个端点的矩形编码。
Step 2 : If both endpoints have a region code 0000 then given line is completely inside.
2:如果两个端点的举行编码都为0000 那么可以直接判断在矩形内。
Step 3 : Else, perform the logical AND operation for both region codes.
3:否则,进行对两个端点的逻辑与操作

        Step 3.1 : If the result is not 0000, then given line is completely outside.
        3.1:如果两个端点的与运算结果不为0,那么可以判断完全在矩形外。
        Step 3.2 : Else line is partially inside.
        3.2:否则线是部分在矩形内,部分在矩形外的。
        Step 3.2.1 : Choose an endpoint of the line that is outside the given rectangle.
        3.2.1:选择一个在矩形外的端点
        Step 3.2.2 : Find the intersection point of the rectangular boundary (based on region code).
        3.2.2:计算这个交点方向和矩形域边界的交点(基于矩形编码)
        Step 3.2.3 : Replace endpoint with the intersection point and update the region code.
        3.2.3:用交点替换这个外部的端点作为新的端点,并更新矩形编码。
        Step 3.2.4 : Repeat step 2 until we find a clipped line either trivially accepted or trivially rejected.
        3.2.4:重复2-4直到我们能够找到一个被剪出来的线,要么接收,要么拒绝。

Step 4 : Repeat step 1 for other lines
4:重复1对其他的线进行操作。
先把核心的源代码贴上,方便想看代码的朋友直接查阅:(本代码主要参考)
图形学算法项目-Cohen-Sutherland

#include <iostream>
#include <stdio.h>
#include <graphics.h>
// Draw Line
void GraphicsCohenSutherland::draw(float x1, float y1, float x2, float y2)
{
    float h = (float)getheight();
    float w = (float)getwidth();
    line(x1 + w / 2, (h / 2) - y1, x2 + w / 2, (h / 2) - y2);
}
// Function to compute region code for a point(x, y).
int GraphicsCohenSutherland::computeCode(double x, double y)
{
    // initialized as being inside
    int code = INSIDE;

    if (x < x_min)       // to the left of rectangle
        code |= LEFT;
    else if (x > x_max)  // to the right of rectangle
        code |= RIGHT;
    if (y < y_min)       // below the rectangle
        code |= BOTTOM;
    else if (y > y_max)  // above the rectangle
        code |= TOP;

    return code;
}
// Clear Screen
void GraphicsCohenSutherland::myClearDevice()
{
    float h = (float)getheight();
    float w = (float)getwidth();
    cleardevice();   /* clear the screen */
    line(0, h / 2, w, h / 2);
    line(w / 2, 0, w / 2, h);
}
// Draw Boundary
void GraphicsCohenSutherland::drawBoundary()
{
    draw(x_min, y_min, x_max, y_min);
    draw(x_min, y_min, x_min, y_max);
    draw(x_max, y_min, x_max, y_max);
    draw(x_min, y_max, x_max, y_max);
}
// Implementing Cohen-Sutherland algorithm
// Clipping a line from P1 = (x1, y1) to P2 = (x2, y2)
void GraphicsCohenSutherland::cohenSutherlandClip(double x1, double y1, double x2, double y2)
{
    // Compute region codes for P1, P2
    int code1 = computeCode(x1, y1);
    //printf("%d\n", code1);
    int code2 = computeCode(x2, y2);
    //printf("%d\n", code2);

    // Initialize line as outside the rectangular window
    bool accept = false;

    while (true)
    {
        if ((code1 == 0) && (code2 == 0))
        {
            // If both endpoints lie within rectangle
            accept = true;
            break;
        }
        else if (code1 & code2)
        {
            // If both endpoints are outside rectangle, in same region
            break;
        }
        else
        {
            // Some segment of line lies within the rectangle
            int code_out;
            double x, y;

            // At least one endpoint is outside the rectangle, pick it.
            if (code1 != 0)
                code_out = code1;
            else
                code_out = code2;

            // Find intersection point; using formulas y = y1 + slope * (x - x1),
            // x = x1 + (1 / slope) * (y - y1)
            if (code_out & TOP)
            {
                // point is above the clip rectangle
                x = x1 + (x2 - x1) * (y_max - y1) / (y2 - y1);
                y = y_max;
            }
            else if (code_out & BOTTOM)
            {
                // point is below the rectangle
                x = x1 + (x2 - x1) * (y_min - y1) / (y2 - y1);
                y = y_min;
            }
            else if (code_out & RIGHT)
            {
                // point is to the right of rectangle
                y = y1 + (y2 - y1) * (x_max - x1) / (x2 - x1);
                x = x_max;
            }
            else if (code_out & LEFT)
            {
                // point is to the left of rectangle
                y = y1 + (y2 - y1) * (x_min - x1) / (x2 - x1);
                x = x_min;
            }

            // Now intersection point x,y is found We replace point outside rectangle by intersection point
            if (code_out == code1)
            {
                x1 = x;
                y1 = y;
                code1 = computeCode(x1, y1);
            }
            else
            {
                x2 = x;
                y2 = y;
                code2 = computeCode(x2, y2);
            }
        }
    }
    if (accept)
    {
        cout << "Line accepted from " << x1 << ", "
            << y1 << " to " << x2 << ", " << y2 << endl;
        // Here the user can add code to display the rectangle
        // along with the accepted (portion of) lines

        myClearDevice();
        drawBoundary();
        draw(x1, y1, x2, y2);

    }
    else
        cout << "Line rejected" << endl;
}

void GraphicsCohenSutherland::TestEnter(int argc, char** argv)
{
    int gd = DETECT, gm;
    //  printf("Press 1 to clip!\n");
    cout << "Press 1 to clip!\n";
    int ans = 1;
    //  scanf_s("%d", &ans);
    cin >> ans;

    // Initialize graphics function
    initgraph(&gd, &gm, "");

    // 获取绘图区宽度和高度
    float h = (float)getheight();
    float w = (float)getwidth();

    // Axis 
    // create the 2D coordinate system.
    line(0, h / 2, w, h / 2);
    line(w / 2, 0, w / 2, h);
    drawBoundary();
    int x1 = - 200, y1 = -200, x2 = 200, y2 = 200;
    draw(x1, y1, x2, y2);

    if (ans == 1)
    {
        cohenSutherlandClip(x1, y1, x2, y2);
    }
    getchar();
}

运行结果为:
运行结果

编码算法

点裁剪

对于任意一点 Pxy P ( x , y ) ,若满足下列两对不等式:

{xleftxxrightybottomyytop { x l e f t ≤ x ≤ x r i g h t y b o t t o m ≤ y ≤ y t o p

那么P点在矩形窗口内,否则,P点在矩形窗口外。
点裁剪算法

直线段裁剪

直线段裁剪算法是复杂图形裁剪的基础算法。
我们可以了解到,直线段和裁剪窗口的可能关系分为三种:

  1. 完全落在窗口内
  2. 完全落在窗口外
  3. 与窗口边界相交

我们如果想要裁剪一条直线段,首先需要判断:

  1. 它是否完全落在裁剪窗口内?
  2. 它是否完全在窗口外?
  3. 如果不满足以上两个条件,则计算它与一个或多个裁剪边界的交点。

我们常用的裁剪算法有三种,即Cohen-Sutherland、中点分割法和LIang-Barsky裁剪算法。

Cohen-Sutherland算法

本算法又称之为编码裁剪算法,算法的基本思想是对每条直线段分三种情况处理:

  1. 若点 p1 p 1 p2 p 2 完全落在裁剪窗口内,则采取简单取之的办法。
    简取之
  2. 若点 p1(x,y) p 1 ( x , y ) p2(x2,y2) p 2 ( x 2 , y 2 ) 均在窗口外,且满足下列四个条件之一,我们将采取简单放弃之的策略:
    x1<xleftx1>xrighty1<ybottomy1>ytopandandandandx2<xleftx2>xrighty2<ybottomy2>ytop { x 1 < x l e f t a n d x 2 < x l e f t x 1 > x r i g h t a n d x 2 > x r i g h t y 1 < y b o t t o m a n d y 2 < y b o t t o m y 1 > y t o p a n d y 2 > y t o p

    简弃
  3. 如果直线段既不满足“简取” ,又不满足“简弃” 的条件。那么我们需要对直线段按交点进行分段,分段之后判断直线是“简取”还是“简弃”。
    分段

那么,我们就引入了编码思想,每条线段的端点都赋以四位二进制码 D3D2D1D0 D 3 D 2 D 1 D 0 ,编码规则如下:

  1. x<xleft x < x l e f t , 则 D0=1 D 0 = 1 , 否则 D0=0 D 0 = 0
  2. x>xright x > x r i g h t , 则 D1=1 D 1 = 1 , 否则 D1=0 D 1 = 0
  3. y>yright y > y r i g h t , 则 D2=1 D 2 = 1 , 否则 D2=0 D 2 = 0
  4. y>yright y > y r i g h t , 则 D3=1 D 3 = 1 , 否则 D3=0 D 3 = 0

范围
所以,根据窗口及其延长线,分为了9个区域,根据该编码规则:

  1. D0 D 0 对应窗口左边界。
  2. D1 D 1 对应窗口右边界。
  3. D2 D 2 对应窗口下边界。
  4. D3 D 3 对应窗口上边界。

窗口区域划分

裁剪一条线段的时候,先求出端点 p1 p 1 p2 p 2 的编码code1和code2,然和利用二进制“或”运算和“与运算”
二进制运算

  1. code1|code2=0 c o d e 1 | c o d e 2 = 0 , 那么简取之。
  2. coe_1 & code_2 \neq 0 coe_1 & code_2 \neq 0 , 那么简弃之。

编码举例

若上述两个条件均不成立,那么则需要求出直线段与窗口边界的交点在交点处吧线段一分为二。
之后,按照算法步骤来裁剪线段 P1,P2 P 1 , P 2
1. 首先对 P1,P2 P 1 , P 2 进行编码。
2. 按左、右、下、上的顺序求出直线段与窗口左边界的交点为 P3 P 3 P1P3 P 1 P 3 必在窗口外,可以简单弃之。
3. 对 P2P3 P 2 P 3 重复上述的处理。
4. 剩下的直线段( P3P4 P 3 P 4 )在进一步判断,直到 code1|code2=0 c o d e 1 | c o d e 2 = 0 ,那么可以判断全在窗口内,简取之即可。

算法实现

具体可以:跳转到顶部

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值