Nicholl-Lee-Nicholl算法裁剪线段

算法简介:

 

Nicholl-Lee-Nicholl(NLN)算法通过在裁剪窗口边界创建多个区域,从而避免对一个直线段进行多次裁剪。

Cohen-Sutherland算法中,在找到与裁剪窗口边界的交点之前或完全舍弃该线段之前,必须对一条线段进行多次求交计算。NLN算法则在求交计算前进行更多的区域测试,从而减少求交计算。

NLN算法的特点是效率高,但该算法仅用于二维裁剪

 


算法描述:

 

如上图,对于 b 这种完全在矩形内的线段,和 a 这种两端点都在矩形同一侧的线段,可以直接采用Cohen-Sutherland编码方法进行取舍(不熟的朋友请参看文章:Cohen-Sutherland算法 ---- https://blog.csdn.net/qq_42185999/article/details/102668328)。

对于 d 这种不能采用上述方法直接判断的线段,还有 c 这种既不是完全可见也不是完全不可见的线段,Nicholl-Lee-Nicholl算法处理步骤如下:

 

第一步:

对于端点为 P和 P1 的线段,首先确定P0位于裁剪窗口的九个可能区域的位置。如下图,我们只需考虑四个区域。若位于其他五个区域之一,则可以利用对称变换将其变换到这四个区域之一。

 

                                

 

第二步:

从 P0 点向窗口的四个角点发出射线,这四条射线和窗口的四条边所在的直线将二维平面划分为更多的小区域。此时 P1 的位置就决定了 P0P1 和窗口边的相交关系。

 

第三步:

确定 P1 所在的区域(判断 P1 所在区域位置,可判断 P0P1 与窗口那条边求交),根据窗口四边的坐标值及 P0P1 和各射线的斜率可确定 P1 所在的区域。

 

第四步:求交点,确定 P0P1 的可见部分

 

 

如上图,当 P0 在框内:

如果 P1 位于 L 区,则只需求 P0P1 与 窗口左边的交点 P_{1}^{}{}'   ,   P_{0}{P_{1}}' 即为所取线段; 

如果 P1 位于 T 区,则只需求 P0P1 与 窗口上边的交点 P_{1}^{}{}'   ,   P_{0}{P_{1}}' 即为所取线段 ; 

如果 P1 位于 R 区,则只需求 P0P1 与 窗口右边的交点 P_{1}^{}{}'   ,   P_{0}{P_{1}}' 即为所取线段 ; 

如果 P1 位于 B 区,则只需求 P0P1 与 窗口底边的交点 P_{1}^{}{}'   ,   P_{0}{P_{1}}' 即为所取线段。

 

如上图,当 P0 在框上侧:

如果 P1 位于 T 区,则只需求 P0P1 与 窗口上边的交点 P_{0}^{}{}'   ,   {P_{0}}'P_{1} 即为所取线段;

如果 P1 位于 LT 区,则需求 P0P1 与 窗口上边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口左边的交点 P_{1}^{}{}' ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

如果 P1 位于 BT 区,则需求 P0P1 与 窗口上边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口底边的交点 P_{1}^{}{}' ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

如果 P1 位于 TR 区,则需求 P0P1 与 窗口上边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口右边的交点 P_{1}^{}{}' ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

 

 

如上图,当 P0 在框左侧:

如果 P1 位于 L 区,则只需求 P0P1 与 窗口左边的交点 P_{0}^{}{}'   ,   {P_{0}}'P_{1} 即为所取线段;

如果 P1 位于 LT 区,则需求 P0P1 与 窗口左边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口上边的交点 P_{1}^{}{}' ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

如果 P1 位于 LR 区,则需求 P0P1 与 窗口左边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口右边的交点 P_{1}^{}{}'​​​​​​​ ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

如果 P1 位于 LB 区,则需求 P0P1 与 窗口左边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口底边的交点 P_{1}^{}{}'​​​​​​​ ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

 

 

 

当P0在窗口的左上方时,有如下图的两种情况:

 

(a) .   P0接近裁剪窗口左边界

如果 P1 位于 L 区,则只需求 P0P1 与 窗口左边的交点 P_{0}^{}{}'   ,   {P_{0}}'P_{1} 即为所取线段;

如果 P1 位于 T 区,则只需求 P0P1 与 窗口上边的交点 P_{0}^{}{}'   ,   {P_{0}}'P_{1} 即为所取线段;

如果 P1 位于 LB 区,则需求 P0P1 与 窗口左边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口底边的交点 P_{1}^{}{}'​​​​​​​ ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

如果 P1 位于 TB 区,则需求 P0P1 与 窗口上边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口底边的交点 P_{1}^{}{}'​​​​​​​ ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

如果 P1 位于 TR 区,则需求 P0P1 与 窗口上边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口右边的交点 P_{1}^{}{}'​​​​​​​ ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

 

 

(b) .   P0接近裁剪窗口上边界

如果 P1 位于 L 区,则只需求 P0P1 与 窗口左边的交点 P_{0}^{}{}'   ,   {P_{0}}'P_{1} 即为所取线段;

如果 P1 位于 T 区,则只需求 P0P1 与 窗口上边的交点 P_{0}^{}{}'   ,   {P_{0}}'P_{1} 即为所取线段;

如果 P1 位于 LB 区,则需求 P0P1 与 窗口左边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口底边的交点 P_{1}^{}{}'​​​​​​​ ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

如果 P1 位于 LR 区,则需求 P0P1 与 窗口左边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口右边的交点 P_{1}^{}{}'​​​​​​​ ,   {P_{0}}'{P_{1}}' 即为所取线段 ; 

如果 P1 位于 TR 区,则需求 P0P1 与 窗口上边的交点 P_{0}^{}{}'   ,  P0P1 与 窗口右边的交点 P_{1}^{}{}'​​​​​​​ ,   {P_{0}}'{P_{1}}' 即为所取线段 ;

 

 

 

算法的细节:

 

判断 P1 具体所在区域:

首先,根据 P0P1 和各射线的斜率可确定 P1 所在的大概区域。然后,根据线段端点 P1 的编码判断 P1 是否在窗口内,进一步判断 P1 所在具体区域。

 

直线与窗口边界的交点可如下求解:

已知直线和其两端点 (X_{1},Y_{1}) , (X_{2},Y_{2}) , 与水平线 Y=K 的交点为 (X,Y)

X=X_{1}+(X_{2}-X_{1})*(K-Y_{1})/(Y_{2}-Y_{1})     

Y=K

与铅垂线 X=R 的交点为

X=R

Y=Y_{1}+(Y_{2}-Y_{1})*(R-X_{1})/(X_{2}-X_{1})     

   

 

核心代码:

(这里贴代码是给大家看看思路,这个代码运行出来有一点问题,坐标转换不对,但是我没检查出来问题,还请各位大佬帮忙看看,谢谢啦!)

    def line_clip(self, line1, rt):
        done = False  # 裁剪完成标志
        p1 = line1.p1()
        print(p1);
        p2 = line1.p2()
        print(p2);
        c1 = self.getcode(p1, rt)
        c2 = self.getcode(p2, rt)
        p = QPoint(0, 0)
        line2 = QLine(p, p)
        biaozhi = True


        if c1 == 0 and c2 == 0:  # 线段完全可见
            line2 = QLine(p1, p2)
        elif c1 & c2 != 0:  # 线段完全不可见
            line2 = QLine(QPoint(0, 0), QPoint(0, 0))
        else:  # 部分可见
            if(c1 == 0x02): #右侧变换
                print("右侧变换");
                p1.setX(2*rt.x()+rt.width()-p1.x())
                p2.setX(2 * rt.x() + rt.width() - p2.x())
            if (c1 == 0x0a):  # 右上侧变换
                print("右上侧变换");
                p1.setX(2 * rt.x() + rt.width() - p1.x())
                p2.setX(2 * rt.x() + rt.width() - p2.x())
            if (c1 == 0x05):  # 左下侧变换
                print("左下侧变换");
                p1.setY(2 * rt.y() + rt.height() - p1.y())
                p2.setY(2 * rt.y() + rt.height() - p2.y())
            if (c1 == 0x06):  # 右下侧变换
                print("右下侧变换");
                p1.setX(2 * rt.x() + rt.width() - p1.x())
                p2.setX(2 * rt.x() + rt.width() - p2.x())
                p1.setY(2 * rt.y() + rt.height() - p1.y())
                p2.setY(2 * rt.y() + rt.height() - p2.y())
            if (c1 == 0x04):  # 下侧变换
                print("下侧变换");
                p1.setY(2 * rt.y() + rt.height() - p1.y())
                p2.setY(2 * rt.y() + rt.height() - p2.y())

            c11 = self.getcode(p1, rt)
            c22 = self.getcode(p2, rt)

            mLT = (p1.y()-rt.top())/(p1.x()-rt.left())
            mTR = (p1.y()-rt.top())/(p1.x()-rt.right())
            mBL = (p1.y() - rt.bottom()) / (p1.x() - rt.left())
            mRB = (p1.y() - rt.bottom()) / (p1.x() - rt.right())
            m = (p1.y()-p2.y())/(p1.x()-p2.x())

            ##   判断起点位置
            if(c11 == 0x00): # 情况一:在框内
                print("在框内");
                # left
                if(m < mBL and m >= mLT):
                    print("L");
                    p2.setX(rt.left())
                    p2.setY(p1.y()+(p2.y()-p1.y())*(rt.left()-p1.x())/(p2.x()-p1.x()))
                #bottom
                if(m >= mBL or m < mRB):
                    print("b");
                    p2.setY(rt.bottom())
                    p2.setX(p1.x()+(p2.x()-p1.x())*(rt.bottom()-p1.y())/(p2.y()-p1.y()))
                #right
                if(m >= mRB and m < mTR):
                    print("r");
                    p2.setX(rt.right())
                    p2.setY(p1.y() + (p2.y() - p1.y()) * (rt.right() - p1.x()) / (p2.x() - p1.x()))
                #top
                if(m >= mTR or m < mLT):
                    print("t");
                    p2.setY(rt.top())
                    p2.setX(p1.x() + (p2.x() - p1.x()) * (rt.top() - p1.y()) / (p2.y() - p1.y()))


            elif (c11 == 0x01):  # 情况二:在框左侧
                print("在框左侧");
                if(m < mBL or m >= mLT):
                    print("diudiao");
                    biaohzi = False
                    #line2 = QLine(QPoint(0, 0), QPoint(0, 0))
                else:
                    #起点剪裁
                    biaohzi = True
                    p1.setX(rt.left())
                    p1.setY(p2.y() + (p1.y() - p2.y()) * (rt.left() - p2.x()) / (p1.x() - p2.x()))
                    if(c22 != 0x00):
                        if(mBL <= m and m < mRB): #LB
                            p2.setY(rt.bottom())
                            p2.setX(p1.x() + (p2.x() - p1.x()) * (rt.bottom() - p1.y()) / (p2.y() - p1.y()))
                        elif(mRB <= m and m < mTR): #LR
                            p2.setX(rt.right())
                            p2.setY(p1.y() + (p2.y() - p1.y()) * (rt.right() - p1.x()) / (p2.x() - p1.x()))
                        elif(mTR <= m and m < mLT): #LT
                            p2.setY(rt.top())
                            p2.setX(p1.x() + (p2.x() - p1.x()) * (rt.top() - p1.y()) / (p2.y() - p1.y()))


            elif (c11 == 0x09):  # 情况三:在框左上侧
                print("在框左上侧");
                if(m < mBL or m >= mTR): #区域外
                    biaozhi = False
                else:
                    biaohzi = True
                    # 起点靠近左裁剪线
                    if((rt.left()-p1.x()) < (p1.y()-rt.top())):
                        if(mBL <= m and m < mLT):  #L
                            p1.setX(rt.left())
                            p1.setY(p2.y() + (p1.y() - p2.y()) * (rt.left() - p2.x()) / (p1.x() - p2.x()))
                            if(c22 != 0x00):   # LB
                                p1.setY(rt.bottom())
                                p1.setX(p2.x() + (p1.x() - p2.x()) * (rt.bottom() - p2.y()) / (p1.y() - p2.y()))
                        elif(mLT <= m and m < mRB):
                            p1.setY(rt.top())
                            p1.setX(p2.x() + (p1.x() - p2.x()) * (rt.top() - p2.y()) / (p1.y() - p2.y()))
                            if(c22 != 0x00): # TB
                                p2.setY(rt.bottom())
                                p2.setX(p1.x() + (p2.x() - p1.x()) * (rt.bottom() - p1.y()) / (p2.y() - p1.y()))

                        elif(mRB <= m and m < mTR):
                            p1.setY(rt.top())
                            p1.setX(p2.x() + (p1.x() - p2.x()) * (rt.top() - p2.y()) / (p1.y() - p2.y()))
                            if (c22 != 0x00): # TR
                                p2.setX(rt.right())
                                p2.setY(p1.y() + (p2.y() - p1.y()) * (rt.right() - p1.x()) / (p2.x() - p1.x()))

                    # 起点靠近上裁剪线
                    else:
                        if(mBL <= m and m < mRB): # L
                            p1.setX(rt.left())
                            p1.setY(p2.y() + (p1.y() - p2.y()) * (rt.left() - p2.x()) / (p1.x() - p2.x()))
                            if(c22 != 0x00):  # LB
                                p2.setY(rt.bottom())
                                p2.setX(p1.x() + (p2.x() - p1.x()) * (rt.bottom() - p1.y()) / (p2.y() - p1.y()))

                        elif(mRB <= m and m < mLT): # L
                            p1.setX(rt.left())
                            p1.setY(p2.y() + (p1.y() - p2.y()) * (rt.left() - p2.x()) / (p1.x() - p2.x()))
                            if(c22 != 0x00):  # LR
                                p2.setX(rt.right())
                                p2.setY(p1.y() + (p2.y() - p1.y()) * (rt.right() - p1.x()) / (p2.x() - p1.x()))

                        elif(mLT <= m and m < mTR): # T
                            p1.setY(rt.top())
                            p1.setX(p2.x() + (p1.x() - p2.x()) * (rt.top() - p2.y()) / (p1.y() - p2.y()))
                            if(c22 != 0x00):  #TR
                                p2.setX(rt.right())
                                p2.setY(p1.y() + (p2.y() - p1.y()) * (rt.right() - p1.x()) / (p2.x() - p1.x()))



            elif (c11 == 0x08): # 情况四:在框上侧
                print("在框上侧");

                if((m > 0 and m < mLT) or (m < 0 and m >= mTR)):
                    biaozhi = False
                    #line2 = QLine(QPoint(0, 0), QPoint(0, 0))
                else:
                    # T
                    p1.setY(rt.top())
                    p1.setX(p2.x() + (p1.x() - p2.x()) * (rt.top() - p2.y()) / (p1.y() - p2.y()))
                    if(c22 != 0x00):
                        if(mLT <= m and m < mBL): #TL
                            p2.setX(rt.left())
                            p2.setY(p1.y() + (p2.y() - p1.y()) * (rt.left() - p1.x()) / (p2.x() - p1.x()))
                        elif(m >= mBL or m <= mRB): # TB
                            p2.setY(rt.bottom())
                            p2.setX(p1.x() + (p2.x() - p1.x()) * (rt.bottom() - p1.y()) / (p2.y() - p1.y()))
                        elif(mRB <= m and m < mTR): # TR
                            p2.setX(rt.right())
                            p2.setY(p1.y() + (p2.y() - p1.y()) * (rt.right() - p1.x()) / (p2.x() - p1.x()))



            #恢复对称区域的变化
            if (c1 == 0x02):  # 右侧变换
                print("hui右侧变换");
                p1.setX(2 * rt.x() + rt.width() - p1.x())
                p2.setX(2 * rt.x() + rt.width() - p2.x())
            if (c1 == 0x0a):  # 右上侧变换
                print("hui右上侧变换");
                p1.setX(2 * rt.x() + rt.width() - p1.x())
                p2.setX(2 * rt.x() + rt.width() - p2.x())
            if (c1 == 0x05):  # 左下侧变换
                print("下侧变换");
                p1.setY(2 * rt.y() + rt.height() - p1.y())
                p2.setY(2 * rt.y() + rt.height() - p2.y())
            if (c1 == 0x06):  # 右下侧变换
                print("下侧变换");
                p1.setX(2 * rt.x() + rt.width() - p1.x())
                p2.setX(2 * rt.x() + rt.width() - p2.x())
                p1.setY(2 * rt.y() + rt.height() - p1.y())
                p2.setY(2 * rt.y() + rt.height() - p2.y())
            if (c1 == 0x04):  # 下侧变换
                print("下侧变换");
                p1.setY(2 * rt.y() + rt.height() - p1.y())
                p2.setY(2 * rt.y() + rt.height() - p2.y())



            if(biaozhi == False):
                line2 = QLine(QPoint(0, 0), QPoint(0, 0))
            else:
                line2 = QLine(p1, p2)


        return line2

 

 

 

PS: 这里是完整代码,欢迎移步:https://pan.baidu.com/s/13Gu5JtZ5DRJIw35_aRPyHQ  进行下载

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Nicholl-Lee-Nicholl(NLN)算法是一种线段裁剪算法,用于在计算机图形学中将线段裁剪到指定的窗口或视口中。它是一种快速而有效的算法,通常用于实时应用程序。 以下是使用C++和EasyX图形库实现NLN算法的代码: ```cpp #include <graphics.h> #include <iostream> using namespace std; // 定义 NLN 算法所需的几个数据结构 struct Point { int x; int y; }; struct Edge { Point p1; Point p2; float dx; float dy; }; struct Window { int left; int top; int right; int bottom; }; // 定义 NLN 算法函数 void NLN(Edge edge, Window window) { float t1 = 0, t2 = 1; float dx = edge.dx; float dy = edge.dy; // 判断线段是否在窗口内 if (dx == 0) { if ((edge.p1.x < window.left) || (edge.p1.x > window.right)) return; } else { float m = dy / dx; float c = edge.p1.y - m * edge.p1.x; float tleft = (window.left - c) / m; float tright = (window.right - c) / m; if (dx > 0) { if (tleft > t1) t1 = tleft; if (tright < t2) t2 = tright; } else { if (tleft < t2) t2 = tleft; if (tright > t1) t1 = tright; } } if (dy == 0) { if ((edge.p1.y < window.top) || (edge.p1.y > window.bottom)) return; } else { float m = dx / dy; float c = edge.p1.x - m * edge.p1.y; float ttop = (window.top - c) / m; float tbottom = (window.bottom - c) / m; if (dy > 0) { if (ttop > t1) t1 = ttop; if (tbottom < t2) t2 = tbottom; } else { if (ttop < t2) t2 = ttop; if (tbottom > t1) t1 = tbottom; } } // 判断线段是否被裁剪 if (t1 <= t2) { Point new_p1, new_p2; new_p1.x = edge.p1.x + t1 * dx; new_p1.y = edge.p1.y + t1 * dy; new_p2.x = edge.p1.x + t2 * dx; new_p2.y = edge.p1.y + t2 * dy; // 绘制裁剪后的线段 line(new_p1.x, new_p1.y, new_p2.x, new_p2.y); } } int main() { // 创建图形窗口 initgraph(640, 480); // 绘制窗口边框 setcolor(WHITE); rectangle(100, 100, 500, 400); // 定义窗口和线段数据 Window window = {100, 100, 500, 400}; Edge edge = {{50, 200}, {600, 200}, 0, 0}; // 计算线段裁剪后的结果并绘制 NLN(edge, window); // 关闭图形窗口 getch(); closegraph(); return 0; } ``` 以上代码中,我们首先定义了三个数据结构:`Point` 代表点的坐标,`Edge` 代表线段的两个端点,以及 `Window` 代表窗口的大小。 然后,我们实现了 `NLN` 函数,该函数接受一个线段和一个窗口作为输入,并计算线段在窗口内的裁剪结果。该函数的实现基于 NLN 算法的基本原理,即通过计算线段与窗口之间的交点来裁剪线段。 最后,我们在 `main` 函数中使用 `NLN` 函数计算线段裁剪结果,并绘制在图形窗口中。在绘制之前,我们还绘制了一个矩形边框来表示窗口的大小。 运行程序后,将看到裁剪后的线段仅在窗口内被绘制,而窗口外的部分被裁剪掉了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值