个人博客: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