个人博客:www.vectormoon.net
算法简介
编码算法是最早、最流行的线段裁剪算法,该算法采用区域检验的方法,能够快速有效地判断一条线段与裁剪窗口的位置关系,对完全接受或完全舍弃的线段无需求交,即可直接识别。
算法思想
编码算法将整个画布分成9个区域,如下图所示:
根据线段端点所在位置,给每个端点一个四位二进制码(称为区域码)。四位区域码的4位从左到右依次表示上、下、右、左。区域码的任何为赋值为1代表端点落在相应的区域中,否则为0。
区域码的生成
区域码的生成有两种方法:
1.比较法
根据上面提到的区域编码规则可知,在确定区域码每位的值时,可通过比较端点坐标值 ( x , y ) (x,y) (x,y)和裁剪边界来确定区域码各位的值:
- 如果 x < x m i n x<x_{min} x<xmin,表示该点在裁剪窗口左边界的左边,则第1位置1,否则置0;
- 如果 x > x m a x x>x_{max} x>xmax,表示该点在裁剪窗口右边界的右边,则第2位置1,否则置0;
- 如果 y < y m i n y<y_{min} y<ymin,表示该点在裁剪窗口下边界的下边,则第3位置1,否则置0;
- 如果 y < y m a x y<y_{max} y<ymax,表示该点在裁剪窗口上边界的上边,则第4位置1,否则置0;
2.差值法
按照下列两步可以确定区域码各位的值:
- 计算端点坐标和裁剪边界之间的差值
- 用各差值符号来设置区域码各位的值:第1位为 x − x m i n x-x_{min} x−xmin的符号位;第2位为 x − x m a x x-x{max} x−xmax;第3位为 y − y m i n y-y_{min} y−ymin的符号位;第4位为 y − y m a x y-y{max} y−ymax的符号位。
对线段的裁剪处理
根据线段和裁剪窗口的关系可分三种情况处理:
-
线段完全在裁剪窗口之内
两个端点的区域码都为0000,则该线段完全在裁剪窗口内。如上图: P 5 P 6 P_5P_6 P5P6 -
线段完全在裁剪窗口之外
两个端点的区域码相与的结果不为0000,则该线段完全在裁剪窗口之外。如上图: P 9 P 10 P_9P_{10} P9P10 -
其他
上图中 P 1 P 2 , P 3 P 4 , P 7 P 8 P_1P_2,P_3P_4,P_7P_8 P1P2,P3P4,P7P8都是此类问题; P 1 P 2 , P 7 P 8 P_1P_2,P_7P_8 P1P2,P7P8显见属于一半落在窗口内一半落在窗口外,需要进行求交运算;而 P 3 P 4 P_3P_4 P3P4虽然完全落在窗口外但是条件不被第二种情况所适用也需要进行求交运算
求交过程:首先对线段外端点(落在窗口外的点)与一条裁剪边界比较来确定需要裁剪多少线段;然后,将线段的剩下部分与其他裁剪边界对比,直到该直线完全落在窗口内或者被舍弃。
实际算法实现只有在检测到区域码的某位为1时,才把线段和对应裁剪窗口进行求交运算。整个算法的流程如下图所示:
代码实现
def Cohen-Sutherland(p_list, x_min, y_min, x_max, y_max):
"""线段裁剪
:param p_list: (list of list of int: [[x0, y0], [x1, y1]]) 线段的起点和终点坐标
:param x_min: 裁剪窗口左上角x坐标
:param y_min: 裁剪窗口左上角y坐标
:param x_max: 裁剪窗口右下角x坐标
:param y_max: 裁剪窗口右下角y坐标
:return: (list of list of int: [[x_0, y_0], [x_1, y_1]]) 裁剪后线段的起点和终点坐标
"""
result = []
if y_min > y_max:
y_min, y_max = y_max, y_min
x0, y0 = p_list[0]
x1, y1 = p_list[1]
while 1:
code0 = 0 #1_left, 2_right, 4_down, 8_up
code1 = 0 #1_left, 2_right, 4_down, 8_up
#calc code0
if x0 < x_min:
code0 += 1
elif x0 > x_max:
code0 += 2
if y0 < y_min:
code0 += 4
elif y0 > y_max:
code0 += 8
#calc code1
if x1 < x_min:
code1 += 1
elif x1 > x_max:
code1 += 2
if y1 < y_min:
code1 += 4
elif y1 > y_max:
code1 += 8
#inside
if (code0 | code1) == 0:
result = [[x0, y0], [x1, y1]]
break
#outside
elif (code0 & code1) != 0:
result.append([0,0])
result.append([0,0])
break
#otherwise
else:
if code0 == 0:
x0, x1 = x1, x0
y0, y1 = y1, y0
code0, code1 = code1, code0
#1_left, 2_right, 4_down, 8_up
if (code0 & 1):
y0 = int(y0 + ((x_min-x0) * (y0-y1)/(x0-x1)) + 0.5)
x0 = x_min
if (code0 & 2):
y0 = int(y0 + ((x_max-x0) * (y0-y1)/(x0-x1)) + 0.5)
x0 = x_max
if (code0 & 4):
x0 = int(x0 + ((y_min-y0) * (x0-x1)/(y0-y1)) + 0.5)
y0 = y_min
if (code0 & 8):
x0 = int(x0 + ((y_max-y0) * (x0-x1)/(y0-y1)) + 0.5)
y0 = y_max
return result