计算机小白也看得懂的Liang-Barsky算法

本文详细介绍了Liang-Barsky算法,一种用于计算机图形学中的线段裁剪方法。算法通过参数方程表示直线,将线段视为有方向的线,并根据入边和出边的概念确定裁剪范围。通过计算四个交点的u值及确定出入边,最终实现线段裁剪。提供的代码实现了该算法,可用于实际编程应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

个人博客: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(x2x1)=x1+uΔxy=y1+u(y2y1)=y1+uΔy0u1;0u1.

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(x2x1)=x1+uΔxy=y1+u(y2y1)=y1+uΔy0u1;0u1.

    我们不妨先考虑下,在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} {xminx1+uΔxxmaxyminy1+uΔyymax

可以看出当
{ 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} {xminx1+uΔxxmaxyminy1+uΔyymax
可化简为:
{ 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)x1xminuΔxxmaxx1u(Δy)y1yminuΔyymaxy1
上面四种情况可以归纳成

u ∗ p k ≤ q k , k = 1 , 2 , 3 , 4 u*p_k\leq q_k,k=1,2,3,4 upkqk,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 qk0,则线段完全在边界内

代码实现

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
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值