计算机小白也看得懂的Liang-Barsky算法
个人博客:www.vectormoon.net算法背景 Liang-Barsky算法由梁友栋和Barsky共同发表,是目前计算机图形学最经典的算法之一。他们认为线段裁剪的问题是:裁剪窗口是二维对象,而线段是一维对象,两个对象的维度不同不便比较。他们给出的解决思路是,将裁剪线段和裁剪窗口看为点集裁剪的结果是两个点集的交集。算法思想 Liang-Barsky算法的主要思想有两部分:用参数方
个人博客:www.vectormoon.net
算法背景
Liang-Barsky算法由梁友栋和Barsky共同发表,是目前计算机图形学最经典的算法之一。他们认为线段裁剪的问题是:裁剪窗口是二维对象,而线段是一维对象,两个对象的维度不同不便比较。他们给出的解决思路是,将裁剪线段和裁剪窗口看为点集裁剪的结果是两个点集的交集。
算法思想
Liang-Barsky算法的主要思想有两部分:
- 用参数方程表示直线
- 将待裁剪直线看作是一个有方向的线
用参数方程表示直线
算法背景中提到Liang-Barsky算法的解决思路是,将裁剪线段和裁剪窗口看为点集裁剪的结果是两个点集的交集。那么裁剪线段如何转换点集呢?很显然用参数方程来表示直线。
设待裁剪线段为 P 1 P 2 P_1P_2 P1P2,其中 P 1 = ( x 1 , y 1 ) , P 2 = ( x 2 , y 2 ) P_1=(x_1,y_1),P_2=(x_2,y_2) P1=(x1,y1),P2=(x2,y2),用参数关系u表示有下图关系:

显见有如下关系:
{ x = x 1 + u ∗ ( x 2 − x 1 ) = x 1 + u ∗ Δ x 0 ≤ u ≤ 1 ; y = y 1 + u ∗ ( y 2 − y 1 ) = y 1 + u ∗ Δ y 0 ≤ u ≤ 1. \begin{cases}x=x_1+u*(x2-x1)=x_1+u*\Delta x & 0\leq u \leq 1;\\y=y_1+u*(y2-y1)=y_1+u*\Delta y & 0\leq u \leq 1.\end{cases} {x=x1+u∗(x2−x1)=x1+u∗Δxy=y1+u∗(y2−y1)=y1+u∗Δy0≤u≤1;0≤u≤1.
当 u = 0 u=0 u=0时, x = x 1 , y = y 1 x=x_1,y=y_1 x=x1,y=y1也就是 P 1 P_1 P1;
当 u = 1 u=1 u=1时, x = x 2 , y = y 2 x=x_2,y=y_2 x=x2,y=y2也就是 P 2 P_2 P2;
当 u = 0.5 u=0.5 u=0.5时,也就是该直线中点位置。
由上面三种情况可以很容易归纳出该图像的几何意义:也就是u值即可表示要裁剪线段的多少
将待裁剪直线看作是一个有方向的线
从上面我们知道u的取值可以决定线段要裁剪的多少,那么u到底如何取值变成了现在的首要目标?
我们将四个窗口的交边分别定义成两类边:入边和出边
- 入边:指从裁剪窗口之外进入到裁剪窗口方向的边
- 出边:指从裁剪窗口之内延伸到窗口之外的边
待裁剪线段和裁剪窗口必定会有四个交点(包括与裁剪窗口延长线的交点)分别设四个交点分别为 c 1 , c 2 , c 3 , c 4 c_1,c_2,c_3,c_4 c1,c2,c3,c4。设待裁剪直线为 P 1 P 2 P_1P_2 P1P2。则有下图:

显见要裁剪线段为 P 1 P_1 P1和 c 3 c_3 c3所夹线段,所以u的选取就要从 P 1 , c 3 P_1,c_3 P1,c3所对应 u 1 , u 2 u_1,u_2 u1,u2入手,则显见有如下关系式:
u 1 = m a x ( c 1 , c 2 , P 1 ) u_1=max(c_1,c_2,P_1) u1=max(c1,c2,P1) u 1 u_1 u1是两个入边和 P 1 P_1 P1对应u值的最小值
u 2 = m i n ( c 3 , P 2 , c 4 ) u_2=min(c_3,P_2,c_4) u2=min(c3,P2,c4) u 2 u_2 u2是两个出边和 P 2 P_2 P2对应u值的最大值
u 1 , u 2 u_1,u_2 u1,u2的值要满足 u 1 < u 2 u_1<u_2 u1<u2
只要求出 u 1 , u 2 u_1,u_2 u1,u2就能算出裁剪线段,但是要求出 u 1 , u 2 u_1,u_2 u1,u2的话,就又出现了两个新的问题:
- 如何算出四个交点 c 1 , c 2 , c 3 , c 4 c_1,c_2,c_3,c_4 c1,c2,c3,c4所对应的u值
- 如何确定哪两个边是出边,哪两个边是入边
四个交点对应的u值
在上面用参数方程表示直线章节中,我们提出了:
{ x = x 1 + u ∗ ( x 2 − x 1 ) = x 1 + u ∗ Δ x 0 ≤ u ≤ 1 ; y = y 1 + u ∗ ( y 2 − y 1 ) = y 1 + u ∗ Δ y 0 ≤ u ≤ 1. \begin{cases}x=x_1+u*(x2-x1)=x_1+u*\Delta x & 0\leq u \leq 1;\\y=y_1+u*(y2-y1)=y_1+u*\Delta y & 0\leq u \leq 1.\end{cases} {x=x1+u∗(x2−x1)=x1+u∗Δxy=y1+u∗(y2−y1)=y1+u∗Δy0≤u≤1;0≤u≤1.
我们不妨先考虑下,在u为何值时, ( x , y ) (x,y) (x,y)位于裁剪窗口之内?我们设裁剪窗口的上边界为 y m a x y_{max} ymax,下边界为 y m i n y_{min} ymin,左边界为 x m i n x_{min} xmin,右边界为 x m a x x_{max} xmax,结合上式有:
{ x m i n ≤ x 1 + u ∗ Δ x ≤ x m a x y m i n ≤ y 1 + u ∗ Δ y ≤ y m a x \begin{cases}x_{min}\leq x_1+u*\Delta x \leq x_{max}\\ y_{min}\leq y_1+u*\Delta y \leq y_{max}\end{cases} {xmin≤x1+u∗Δx≤xmaxymin≤y1+u∗Δy≤ymax
可以看出当
{ x 1 + u ∗ Δ x = x m i n x 1 + u ∗ Δ x = x m a x y 1 + u ∗ Δ y = y m i n y 1 + u ∗ Δ y = y m a x \begin{cases}x_1+u*\Delta x=x_{min}\\ x_1+u*\Delta x=x_{max}\\y_1+u*\Delta y = y_{min}\\y_1+u*\Delta y = y_{max}\end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧x1+u∗Δx=xminx1+u∗Δx=xmaxy1+u∗Δy=yminy1+u∗Δy=ymax
时,为裁剪直线和四个边界的交点值,所以我们可以很轻松的算出四个对应的u值,此处不在赘述。
出入边的确定
上面我们只提到了不等式的四个特殊情况,不失一般性,这里我们写出不等式的所有情况:
{ x m i n ≤ x 1 + u ∗ Δ x ≤ x m a x y m i n ≤ y 1 + u ∗ Δ y ≤ y m a x \begin{cases}x_{min}\leq x_1+u*\Delta x \leq x_{max}\\ y_{min}\leq y_1+u*\Delta y \leq y_{max}\end{cases} {xmin≤x1+u∗Δx≤xmaxymin≤y1+u∗Δy≤ymax
可化简为:
{ u ∗ ( − Δ x ) ≤ x 1 − x m i n u ∗ Δ x ≤ x m a x − x 1 u ∗ ( − Δ y ) ≤ y 1 − y m i n u ∗ Δ y ≤ y m a x − y 1 \begin{cases} u*(-\Delta x) \leq x_1 - x_{min}\\ u*\Delta x \leq x_{max} - x_1\\ u*(-\Delta y) \leq y_1 - y_{min}\\ u*\Delta y \leq y_{max} - y_1\\ \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧u∗(−Δx)≤x1−xminu∗Δx≤xmax−x1u∗(−Δy)≤y1−yminu∗Δy≤ymax−y1
上面四种情况可以归纳成
u ∗ p k ≤ q k , k = 1 , 2 , 3 , 4 u*p_k\leq q_k,k=1,2,3,4 u∗pk≤qk,k=1,2,3,4
使用穷举法可知:
- 当 p k < 0 p_k<0 pk<0时,线段从裁剪边界延长线的外部延伸到内部,也就是入边
- 当 p k > 0 p_k>0 pk>0时,线段从裁剪边界延长线的内部延伸到外部,也就是出边
显见,当 p k = 0 p_k=0 pk=0时,且 q k < 0 q_k<0 qk<0,则线段完全在边界外;若 q k ≥ 0 q_k\geq 0 qk≥0,则线段完全在边界内
代码实现
def Liang-Barsky(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]
p = [x0-x1, x1-x0, y0-y1, y1-y0]
q = [x0-x_min, x_max-x0, y0-y_min, y_max-y0]
u0, u1 = 0, 1
for i in range(4):
if p[i] < 0:
u0 = max(u0, q[i]/p[i])
elif p[i] > 0:
u1 = min(u1, q[i]/p[i])
elif (p[i] == 0 and q[i] < 0):
result = [[0,0], [0,0]]
return result
if u0 > u1:
result = [[0,0], [0,0]]
return result
if u0 > 0:
res_x0 = int(x0 + u0*(x1-x0) + 0.5)
res_y0 = int(y0 + u0*(y1-y0) + 0.5)
if u1 < 1:
res_x1 = int(x0 + u1*(x1-x0) + 0.5)
res_y1 = int(y0 + u1*(y1-y0) + 0.5)
result = [[res_x0, res_y0], [res_x1, res_y1]]
return result
更多推荐



所有评论(0)