Polygon scan line generation based on Y-direction coherence algorithm (applicable to convex polygons and concave polygons) [Principle + Java implementation]

Problem introduction

Given a polygon, it may be a convex polygon or a concave polygon. Now you need to generate a series of lines to describe the polygon. An example is as shown below
Insert image description here

original method

When encountering this problem, the first method that everyone thinks of may be: use a series of vertical lines to intersect the polygon to get several intersection points, then sort the intersection points in ascending order according to the z-axis coordinate value, and finally use the two points as one Groups to form scan lines. This is indeed easy to understand, but the performance is not good because it needs to find the intersection points multiple times and sort the intersection points multiple times.

Y-direction coherence algorithm

This algorithm is mainly used to solve the two performance problems mentioned above: multiple intersection points and multiple sorting.

Avoid finding intersection points multiple times

How to avoid finding intersection points multiple times? In fact, it is very simple, just use the information of the straight line function y=kx+b. For example, if x does not increase by 1, y will increase by k. As in the example below, if the coordinates of point P are known from the beginning, then the intersection point of the line segment with scan line 1 and scan line 2 does not need to be calculated using the straight line intersection formula. It can be obtained directly using y=kx+b
Insert image description here

How to avoid multiple sorting

As shown in the figure below, when the scan line moves between You only need to update the coordinates of the two points based on the expression of the straight line, and there is no need to sort the two points. When x>10, there are new edges that intersect with the scan line. At this time, more intersection points will appear. Only then do you need to sort the intersection points, which greatly reduces the number of sorting times.

Insert image description here

How to achieve

First, you need to maintain an edge table, traverse each edge of the polygon, and put the edge into the corresponding bucket; the second step is to maintain a valid edge set and start scanning and moving y to the right. The initial value of y is among all the points of the polygon. The smallest y mainly does three things during the movement:

  • Are any edges failing? When the scan line cannot be scanned, the edge becomes invalid and is removed from the valid edge set.
  • Are there any new valid edges added? As the scan line moves, when the scan line touches a new line, it needs to be added to the valid edge set. At this time, a new intersection point will be generated. Note that this time needs to be reordered.
  • Every time the scan line moves a distance deltaY along the y-axis, z changes by k*deltaY
    Insert image description here

Code

[Entity class: Edge, used to store data in edge tables and valid edge collections]

package com.dam.entity.sanLine;

/**
 * @Author dam
 * @create 2023/9/15 14:38
 */
public class Edge {
    
    
    public double z;
    public double yMax;
    /**
     * y加一时,z的增量
     */
    public double k;

    public Edge(double z, int yMax, double k) {
    
    
        this.z = z;
        this.yMax = yMax;
        this.k = k;
    }
}

[Method for generating longitudinal scan lines for part point sets]

/**
 * 扫描线生成,使用连贯性算法
 *
 * @param part
 */
private void vScanLineConstruct1(Part part) {
    
    
    List<Integer> vSLineList = new ArrayList<>();
    // 边表
    HashMap<Integer, List<Edge>> edgeTable = new HashMap<>();

    /*
    边表构造
    遍历每一条边,将边的信息放入到相应的桶中,即放入边的两点中y值较小的那个桶中
     */
    for (int i = 0; i < part.offSetOuterContour.size(); i++) {
    
    
        double[] pointI = part.offSetOuterContour.get((i) % part.offSetOuterContour.size());
        double[] pointJ = part.offSetOuterContour.get((i + 1) % part.offSetOuterContour.size());
        // 两个点中较小的y
        int yMin = Math.min((int) Math.round(pointI[0]), (int) Math.round(pointJ[0]));
        int yMax = Math.max((int) Math.round(pointI[0]), (int) Math.round(pointJ[0]));
        if (yMin == yMax) {
    
    
            // 对于垂直线,不需要添加到边表中
            continue;
        }
        double z = (int) Math.round(pointI[0]) < (int) Math.round(pointJ[0]) ? pointI[1] : pointJ[1];
        Edge edge = new Edge((int) Math.round(z), yMax, MathUtil.getKOfLine(pointI[0], pointI[1], pointJ[0], pointJ[1]));
        if (!edgeTable.containsKey(yMin)) {
    
    
            List<Edge> edgeList = new ArrayList<>();
            edgeList.add(edge);
            edgeTable.put(yMin, edgeList);
        } else {
    
    
            edgeTable.get(yMin).add(edge);
        }
    }

    /*
    扫描线构造
     */
    List<Edge> activeEdgeList = new ArrayList<>();
    for (int y = 0; y < part.pixelNumInWidDirection; y++) {
    
    
        /// 判断是否有无效边需要移除
        int i = 0;
        while (i < activeEdgeList.size()) {
    
    
            Edge edge = activeEdgeList.get(i);
            if (edge.yMax == y) {
    
    
                // 当边的yMax==y,该边开始无效,移除边
                activeEdgeList.remove(i);
            } else {
    
    
                i++;
            }
        }

        /// 判断是否有新的有效边加入,如果有的话,需要重新排序
        List<Edge> edgeList = edgeTable.get(y);
        if (edgeList != null && edgeList.size() > 0) {
    
    
            // 需要将新的边添加到有效边集合中
            activeEdgeList.addAll(edgeList);
            // 因为有新边加入,需要重新排序,首先优先按照z的值来升序排序,对于z相同的,按照k升序排序
            Collections.sort(activeEdgeList, ((o1, o2) -> {
    
    
                if (o1.z > o2.z) {
    
    
                    return 1;
                } else if (o1.z < o2.z) {
    
    
                    return -1;
                } else {
    
    
                    if (o1.k > o2.k) {
    
    
                        return 1;
                    } else if (o1.k < o2.k) {
    
    
                        return -1;
                    } else {
    
    
                        return 0;
                    }
                }
            }));
        }

        /// 构造扫描线
        for (int j = 0; j < activeEdgeList.size(); j += 2) {
    
    
            vSLineList.add(y);
            vSLineList.add((int)activeEdgeList.get(j).z);
            vSLineList.add((int)Math.ceil(activeEdgeList.get(j + 1).z));
            // 进行增量计算,将z的值增加
            activeEdgeList.get(j).z += activeEdgeList.get(j).k;
            activeEdgeList.get(j + 1).z += activeEdgeList.get(j + 1).k;
        }
    }
    vLineListSort(vSLineList);
    part.vSLineList = vSLineList;
}

Of course, you cannot call this scan line generation method directly, because I have not released the code of the entity class Part. Readers only need to make some modifications based on the above ideas, which is very simple. In addition, the above is the method of generating vertical scan lines. The method of generating horizontal scan lines is also similar. Just draw inferences from one example.

Effect test

Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here
Insert image description here

Guess you like

Origin blog.csdn.net/laodanqiu/article/details/132912392