算法简介:
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算法处理步骤如下:
第一步:
对于端点为 P0 和 P1 的线段,首先确定P0位于裁剪窗口的九个可能区域的位置。如下图,我们只需考虑四个区域。若位于其他五个区域之一,则可以利用对称变换将其变换到这四个区域之一。
第二步:
从 P0 点向窗口的四个角点发出射线,这四条射线和窗口的四条边所在的直线将二维平面划分为更多的小区域。此时 P1 的位置就决定了 P0P1 和窗口边的相交关系。
第三步:
确定 P1 所在的区域(判断 P1 所在区域位置,可判断 P0P1 与窗口那条边求交),根据窗口四边的坐标值及 P0P1 和各射线的斜率可确定 P1 所在的区域。
第四步:求交点,确定 P0P1 的可见部分
如上图,当 P0 在框内:
如果 P1 位于 L 区,则只需求 P0P1 与 窗口左边的交点 , 即为所取线段;
如果 P1 位于 T 区,则只需求 P0P1 与 窗口上边的交点 , 即为所取线段 ;
如果 P1 位于 R 区,则只需求 P0P1 与 窗口右边的交点 , 即为所取线段 ;
如果 P1 位于 B 区,则只需求 P0P1 与 窗口底边的交点 , 即为所取线段。
如上图,当 P0 在框上侧:
如果 P1 位于 T 区,则只需求 P0P1 与 窗口上边的交点 , 即为所取线段;
如果 P1 位于 LT 区,则需求 P0P1 与 窗口上边的交点 , P0P1 与 窗口左边的交点 , 即为所取线段 ;
如果 P1 位于 BT 区,则需求 P0P1 与 窗口上边的交点 , P0P1 与 窗口底边的交点 , 即为所取线段 ;
如果 P1 位于 TR 区,则需求 P0P1 与 窗口上边的交点 , P0P1 与 窗口右边的交点 , 即为所取线段 ;
如上图,当 P0 在框左侧:
如果 P1 位于 L 区,则只需求 P0P1 与 窗口左边的交点 , 即为所取线段;
如果 P1 位于 LT 区,则需求 P0P1 与 窗口左边的交点 , P0P1 与 窗口上边的交点 , 即为所取线段 ;
如果 P1 位于 LR 区,则需求 P0P1 与 窗口左边的交点 , P0P1 与 窗口右边的交点 , 即为所取线段 ;
如果 P1 位于 LB 区,则需求 P0P1 与 窗口左边的交点 , P0P1 与 窗口底边的交点 , 即为所取线段 ;
当P0在窗口的左上方时,有如下图的两种情况:
(a) . P0接近裁剪窗口左边界
如果 P1 位于 L 区,则只需求 P0P1 与 窗口左边的交点 , 即为所取线段;
如果 P1 位于 T 区,则只需求 P0P1 与 窗口上边的交点 , 即为所取线段;
如果 P1 位于 LB 区,则需求 P0P1 与 窗口左边的交点 , P0P1 与 窗口底边的交点 , 即为所取线段 ;
如果 P1 位于 TB 区,则需求 P0P1 与 窗口上边的交点 , P0P1 与 窗口底边的交点 , 即为所取线段 ;
如果 P1 位于 TR 区,则需求 P0P1 与 窗口上边的交点 , P0P1 与 窗口右边的交点 , 即为所取线段 ;
(b) . P0接近裁剪窗口上边界
如果 P1 位于 L 区,则只需求 P0P1 与 窗口左边的交点 , 即为所取线段;
如果 P1 位于 T 区,则只需求 P0P1 与 窗口上边的交点 , 即为所取线段;
如果 P1 位于 LB 区,则需求 P0P1 与 窗口左边的交点 , P0P1 与 窗口底边的交点 , 即为所取线段 ;
如果 P1 位于 LR 区,则需求 P0P1 与 窗口左边的交点 , P0P1 与 窗口右边的交点 , 即为所取线段 ;
如果 P1 位于 TR 区,则需求 P0P1 与 窗口上边的交点 , P0P1 与 窗口右边的交点 , 即为所取线段 ;
算法的细节:
判断 P1 具体所在区域:
首先,根据 P0P1 和各射线的斜率可确定 P1 所在的大概区域。然后,根据线段端点 P1 的编码判断 P1 是否在窗口内,进一步判断 P1 所在具体区域。
直线与窗口边界的交点可如下求解:
已知直线和其两端点 , , 与水平线 Y=K 的交点为 (X,Y)
与铅垂线 X=R 的交点为
核心代码:
(这里贴代码是给大家看看思路,这个代码运行出来有一点问题,坐标转换不对,但是我没检查出来问题,还请各位大佬帮忙看看,谢谢啦!)
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 进行下载