线段求交应用之Liang-barsky裁剪算法

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

线段剪裁作用

所谓线段剪裁,就是在二维平面上有一堆线段,和一个矩形窗口。求出现在窗口里线段部分是哪些。


上图中绿色为线段,红色为窗口。

剪裁后效果如下。


在这里我们规定矩形的边是平行于x轴和y轴的。

朴素思想及优化

朴素做法就是对于某一条段,用4个边界分别与线段求交。再判断交点的关系,决定哪段在剪裁窗口内。这种方法计算量大,情况也非常复杂。

优化

之前有基于解方程CohenSutherland算法
本文是基于参数化线段方程求交的Liang-barsky算法。

请添加图片描述

窗口左右下上分别为 xWmin, xWmax, yWmin, yWmax。

设 一 条 线 段 的 两 个 端 点 为 P 0 ( x 0 , y 0 ) , P e n d ( x e n d , y e n d ) 设一条线段的两个端点为P_0(x_0, y_0), P_{end}(x_{end}, y_{end}) 线P0(x0,y0),Pend(xend,yend)

∆ x = x e n d − x 0 , ∆ y = y e n d − y 0 ∆x = x_{end}-x_0, ∆y = y_{end}-y_0 x=xendx0,y=yendy0

x W m i n < = x 0 + u ∗ ∆ x < = x W m a x ( 0 < = u < = 1 ) xWmin<= x_0 + u*∆x <= xWmax (0<=u<=1) xWmin<=x0+ux<=xWmax(0<=u<=1)

y W m i n < = y 0 + u ∗ ∆ y < = y W m a x ( 0 < = u < = 1 ) yWmin<= y_0 + u*∆y <= yWmax (0<=u<=1) yWmin<=y0+uy<=yWmax(0<=u<=1)

上述不等式拆成4个不等式。

u ∗ − ∆ x < = x 0 − x W m i n u*-∆x <= x_0-xWmin ux<=x0xWmin

u ∗ ∆ x < = x W m a x − x 0 u*∆x <= xWmax-x_0 ux<=xWmaxx0

u ∗ − ∆ y < = y 0 − y W m i n u*-∆y<= y_0-yWmin uy<=y0yWmin

u ∗ ∆ y < = y W m a x − y 0 u*∆y <= yWmax-y_0 uy<=yWmaxy0

从上式中可以看出,当∆x 或 ∆y=0 且 不等式后边的结果是负数时,不等式是无解的,也就是u没有效范围,反映到线段上就是完全被丢弃。如下图,绿色的线将被丢弃。

请添加图片描述
通过上述4个式子可以解出u的范围,再与[0,1]取交集,就是有效的区间。

一开始,有效区间为[u1, u2], u1=0, u2=1

将上述4个式子整理一下

u ∗ p k < = q k ,   k = 1 , 2 , 3 , 4 u*p_k<=q_k,\ k=1,2,3,4 upk<=qk, k=1,2,3,4

对于一个p, q 如果 p>0 就会解出u<=q/p, 则更新u2为min(u, u2)。
如果p<0 , 就会解出u>=q/p, 则更新u1为max(u, u1)。
如果p==0, 判断是否在区域外。

请添加图片描述
上图展示了左右两边对直线进行剪裁,会确定一个最终区间。
x0>xend时,也有类似的结论。
对于下上边的剪裁也是同理。

线段求交点击前往

实现

#include "glew/2.2.0_1/include/GL/glew.h"
#include "glfw/3.3.4/include/GLFW/glfw3.h"
#include <iostream>
using namespace std;


void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    //如果按下ESC,把windowShouldClose设置为True,外面的循环会关闭应用
    if(key==GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
    std::cout<<"ESC"<<mode;
}

const double esp = 1e-6;

int doubleCmp(double a, double b) {
    if (fabs(a-b)< esp)return 0;
    if (a<b)return -1;
    return 1;
}

class Point {
public:
    double x, y;
    Point(int xx, int yy):x(xx), y(yy){}
};

void setPixel(Point p) {
    // cout<<p.x<<","<<p.y<<endl;
    glPointSize(4);
    glBegin(GL_POINTS);
    glVertex2d(p.x, p.y);
    glEnd();
    glFlush();
}

/*
 * 简单Bresenham 算法
 * 只处理|m|>1
 */
void LineBres_2(Point p1, Point p2) {
    if(p2.y<p1.y) {
        Point t = p2;
        p2=p1;
        p1=t;
    }

    int deltaX = abs(p2.x-p1.x), deltaY = abs(p2.y-p1.y);
    int p0 = 2*deltaX-deltaY;
    int twDeltaX = 2*deltaX, twDeltaXmTwoDeltaY = 2*deltaX-2*deltaY;
    int step = 0;
    if(deltaX>0) step = (p2.x-p1.x) / deltaX;
    setPixel(p1);
    for (; p1.y < p2.y;) {
        p1.y++;
        if(p0<0) {
            p0+=twDeltaX;
        } else {
            p0+=twDeltaXmTwoDeltaY;
            p1.x+=step;
        }
        setPixel(p1);
        // cout<<p1.x<<","<<p1.y<<endl;
    }

}

/*
 * 简单Bresenham 算法
 * 只处理|m|<=1
 */
void LineBres_1(Point p1, Point p2) {
    if(p2.x<p1.x) {
        Point t = p2;
        p2=p1;
        p1=t;
    }

    int deltaX = abs(p2.x-p1.x), deltaY = abs(p2.y-p1.y);
    if(deltaX<deltaY) {
        LineBres_2(p1, p2);
        return;
    }
    int p0 = 2*deltaY-deltaX;
    int twDeltaY = 2*deltaY, twDeltaYmTwoDeltaX = 2*deltaY-2*deltaX;
    int step = 0;
    if(deltaY>0) step = (p2.y-p1.y) / deltaY;
    setPixel(p1);
    for (; p1.x < p2.x;) {
        p1.x++;
        if(p0<0) {
            p0+=twDeltaY;
        } else {
            p0+=twDeltaYmTwoDeltaX;
            p1.y+=step;
        }
        setPixel(p1);
        // cout<<p1.x<<","<<p1.y<<endl;
    }

}


/*
P0(X0, Y0), Pend(Xend, Yend)
∆x = Xend-X0, ∆y = Yend-Y0.
xWmin<= X0 + u*∆x <= xWmax (0<=u<=1),
yWmin<= Y0 + u*∆y <= yWmax (0<=u<=1),

拆解上述不等可以得到


 */
const int xWmin = -100, xWmax = 100, yWmin = -100, yWmax = 100;

bool clipTest(double p, double q, double &u1, double &u2){
    int d = doubleCmp(p, 0);
    if(0==d) {
        return doubleCmp(q,0)>=0;
    }

    double r = q/p;
    if (d<0) {
        if(doubleCmp(r, u2)>0)return false;
        u1 = max(r, u1);
    } else {
        if(doubleCmp(r, u1)<0)return false;
        u2 = min(r, u2);
    }
    return true;
}

void LiangBarsky(Point p1, Point p2) {
    double u1 = 0.0, u2 = 1.0;
    int dx = p2.x-p1.x;
    int dy = p2.y-p1.y;

    if(!clipTest(-dx, p1.x-xWmin, u1, u2))return;
    if(!clipTest(dx, xWmax-p1.x, u1, u2))return;
    if(!clipTest(-dy, p1.y-yWmin, u1, u2))return;
    if(!clipTest(dy, yWmax-p1.y, u1, u2))return;
    if (u2<1) {
        p2.x = p1.x + u2*dx;
        p2.y = p1.y + u2*dy;
    }

    if (u1>0) {
        p1.x += u1*dx;
        p1.y += u1*dy;
    }

    LineBres_1(p1, p2);
}

int main(void) {
    //初始化GLFW库
    if (!glfwInit())
        return -1;
    //创建窗口以及上下文
    GLFWwindow *window = glfwCreateWindow(400, 400, "hello world", NULL, NULL);
    if (!window) {
        //创建失败会返回NULL
        glfwTerminate();
    }

    //建立当前窗口的上下文
    glfwMakeContextCurrent(window);

    glfwSetKeyCallback(window, key_callback); //注册回调函数
    //glViewport(0, 0, 400, 400);
    gluOrtho2D(-200, 200.0, -200, 200.0);
    //循环,直到用户关闭窗口
    cout<<123<<endl;
    while (!glfwWindowShouldClose(window)) {
        /*******轮询事件*******/
        glfwPollEvents();
        // cout<<456<<endl;
        //选择清空的颜色RGBA
        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        // glColor3f(0,0, 0);
        glMatrixMode(GL_PROJECTION);

/*
        //开始画一个三角形
        glBegin(GL_TRIANGLES);
        glColor3f(1, 0, 0); //Red
        glVertex3f(0.5, 0, 0);

        glVertex3f(-1, -1, 0);

        glVertex3f(1, -1, 0);
        //结束一个画图步骤
        glEnd();
*/
/*
        int point1[] = {50, 100};
        glLineWidth(4.0f);
        glBegin(GL_LINE_STRIP);
        glColor3f(100,100, 100);
        glVertex2i(10, 10);

        glColor3f(100,0, 0);
        glVertex2i(20, 20);

        glEnd();

        glBegin(GL_POINTS);
        glVertex2i(100,100);
        glEnd();
*/
        //LineDDA(Point(0,0),Point(60,100));
        //LineDDA(Point(0,0),Point(100,60));

        // 画边框
        glColor3f(1, 0, 0); //Red
        LineBres_1(Point(xWmin,yWmin),Point(xWmin,yWmax));
        LineBres_1(Point(xWmin,yWmin),Point(xWmax,yWmin));
        LineBres_1(Point(xWmax,yWmax),Point(xWmax,yWmin));
        LineBres_1(Point(xWmin,yWmax),Point(xWmax,yWmax));

        glColor3f(0, 1, 0); //green
        // 剪裁前线段
        /*
               LineBres_1(Point(150,-100),Point(150,0));
               LineBres_1(Point(145,-100),Point(-150,100));
               LineBres_1(Point(0,-150),Point(0,150));
               LineBres_1(Point(-150,0),Point(130,0));
               LineBres_1(Point(145,50),Point(-150,200));
               LineBres_1(Point(145,150),Point(-150,200));
               LineBres_1(Point(145,-50),Point(-150,-150));
               LineBres_1(Point(145,-120),Point(-150,-170));
               LineBres_1(Point(145,-150),Point(-150,-100));
               LineBres_1(Point(145,-150),Point(-150,-30));
               LineBres_1(Point(-150,-30), Point(145,150));
               LineBres_1(Point(-150,-30), Point(-145,150));
               LineBres_1(Point(-0,-130), Point(-145,-150));
        */
        // glColor3f(0, 0, 1); //green
        // 剪裁后线段
        LiangBarsky(Point(150,-100),Point(150,0));
        LiangBarsky(Point(145,-100),Point(-150,100));
        LiangBarsky(Point(0,-150),Point(0,150));
        LiangBarsky(Point(-150,0),Point(130,0));
        LiangBarsky(Point(145,50),Point(-150,200));
        LiangBarsky(Point(145,150),Point(-150,200));
        LiangBarsky(Point(145,-50),Point(-150,-150));
        LiangBarsky(Point(145,-120),Point(-150,-170));
        LiangBarsky(Point(145,-150),Point(-150,-100));
        LiangBarsky(Point(145,-150),Point(-150,-30));
        LiangBarsky(Point(-150,-30), Point(145,150));
        LiangBarsky(Point(-150,-30), Point(-145,150));
        LiangBarsky(Point(-0,-130), Point(-145,-150));

        /******交换缓冲区,更新window上的内容******/
        glfwSwapBuffers(window);
        //break;
    }
    glfwTerminate();
    return 0;
}

算法效果

剪裁前效果

剪裁后效果


本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值