Inside Outside测试的2种算法:偶数和绕组数

244 阅读2分钟

在计算机图形学中,Inside Outside是用来测试一个给定的点是否位于一个封闭的多边形内。主要有两种方法来确定一个点是否在多边形的内部/外部:

  1. 偶数-奇数/奇数-偶数规则或奇数平分规则
  2. 绕数法

evenodd

偶数规则/奇数奇偶规则

它也被称为交叉数和射线铸造算法。该算法遵循一个基本的观察,即如果一条来自无穷大的射线穿过多边形的边界,那么它就会从外到内和从外到内交替进行。对于每两个交叉点,点位于多边形的外面。

算法

  1. 构建一条从待测点到多边形外侧点的线段。
  2. 计算线段与多边形边界的交叉点的数量。
  3. 如果交点数量为奇数,则点位于多边形内。
  4. 否则,点位于多边形之外。

even-odd

伪代码如下:

count = 0
foreach side in polygon:
	if line_intersect_side(P,side):
		count = count + 1
if count % 2 == 1:
	return inside
else
	return outside

在线段与顶点相交的情况下,该测试失败。为了处理这个问题,我们做了一些修改。看一下多边形的两条线段的其他端点:

  • 如果端点位于所建线段的同一侧,则考虑该交点的偶数交点
  • 如果端点位于它的对面,则考虑奇数的交点

例子
在图(a)中,可以看出,有奇数交点的点位于多边形内,反之亦然。在图(b)中,代表特殊情况,奇数/偶数是根据边的方向来计算的。

偶数-奇数规则/奇数奇偶规则的实施样本:

// crossing number and ray casting algorithm
// iq.opengenus.org
import static java.lang.Math.*;
 
public class RayCasting {
 
    static boolean intersects(int[] A, int[] B, double[] P) {
        if (A[1] > B[1])
            return intersects(B, A, P);
 
        if (P[1] == A[1] || P[1] == B[1])
            P[1] += 0.0001;
 
        if (P[1] > B[1] || P[1] < A[1] || P[0] >= max(A[0], B[0]))
            return false;
 
        if (P[0] < min(A[0], B[0]))
            return true;
 
        double red = (P[1] - A[1]) / (double) (P[0] - A[0]);
        double blue = (B[1] - A[1]) / (double) (B[0] - A[0]);
        return red >= blue;
    }
 
    static boolean inside_out(int[][] shape, double[] pnt) {
        boolean inside = false;
        int len = shape.length;
        for (int i = 0; i < len; i++) {
            if (intersects(shape[i], shape[(i + 1) % len], pnt))
                inside = !inside;
        }
        return inside;
    }
 
    public static void main(String[] a) {
        double[][] testPoints = {{10, 10}, {10, 16}, {-20, 10}, {0, 10},
        {20, 10}, {16, 10}, {20, 20}};
 
        for (int[][] shape : shapes) {
            for (double[] pnt : testPoints)
                System.out.printf("%7s ", inside_out(shape, pnt));
            System.out.println();
        }
    }
 
    final static int[][] square = {{0, 0}, {20, 0}, {20, 20}, {0, 20}};

    final static int[][] hexagon = {{6, 0}, {14, 0}, {20, 10}, {14, 20},
    {6, 20}, {0, 10}};
 
    final static int[][][] shapes = {square, hexagon};
}

时间复杂度:

  • O(S),其中S是多边形中的边数。

绕数/非零算法

进行测试的另一种算法是缠绕数算法。对于多边形的给定点,将计算出一个绕行数。如果缠绕数为非零,则点位于多边形内。否则,它就位于多边形之外。

绕行数的计算

从概念上讲,要检查一个点P,构建一条从P开始到边界点的线段,将线段视为钉在P处的弹性。检查松紧带在P点周围缠绕了多少次,如果计数为非零,则点位于多边形内。否则,就在多边形的外面。

Winding_Number_Animation_Small

另一种评分方法是为每个与多边形边界相交的点分配一个分数,并将这些数字相加。分数是通过考虑多边形的边缘相对于所建线段的方向而给出的。因此,多边形的每条边都以逆时针的方式分配方向。如果边从所建线的下方开始,则得分-1。如果边从构造线的上方开始,那么得分是1。否则,得分是0。

实施样本:

# Winding Number / Non-Zero Algorithm
# iq.opengenus.org
def winding_number_util(P, V):
    winding_number = 0   # the winding number counter

    # repeat the first vertex at end
    V = tuple(V[:]) + (V[0],)

    # loop through all edges of the polygon
    # edge from V[i] to V[i+1]
    for i in range(len(V)-1):    
        # start y <= P[1]
        if V[i][1] <= P[1]:   
            # an upward crossing
            if V[i+1][1] > P[1]:     
                # P left of edge
                if is_left(V[i], V[i+1], P) > 0: 
                    # have a valid up intersect
                    winding_number += 1           
        # start y > P[1] (no test needed)
        else:                      
            # a downward crossing
            if V[i+1][1] <= P[1]:    
                # P right of edge
                if is_left(V[i], V[i+1], P) < 0:
                    # have a valid down intersect
                    winding_number -= 1           
    return wn

时间复杂度:

  • O(S),其中S是多边形中的边数。

winding-number

例子
对于一个给定的图形,
1. 对于最上面的点,绕数=(-1)+(1)+(-1)+(1)=0,位于外面
2. 对于最下面的点,绕数=-1,位于内部。

通过OpenGenus的这篇文章,你一定对Inside Outside测试有了很好的了解。