How can I determine whether a 2D Point is within a Polygon?

前端 未结 30 2147
醉梦人生
醉梦人生 2020-11-21 05:06

I\'m trying to create a fast 2D point inside polygon algorithm, for use in hit-testing (e.g. Polygon.contains(p:Point)). Suggestions for effective tech

相关标签:
30条回答
  • 2020-11-21 05:50

    Heres a point in polygon test in C that isn't using ray-casting. And it can work for overlapping areas (self intersections), see the use_holes argument.

    /* math lib (defined below) */
    static float dot_v2v2(const float a[2], const float b[2]);
    static float angle_signed_v2v2(const float v1[2], const float v2[2]);
    static void copy_v2_v2(float r[2], const float a[2]);
    
    /* intersection function */
    bool isect_point_poly_v2(const float pt[2], const float verts[][2], const unsigned int nr,
                             const bool use_holes)
    {
        /* we do the angle rule, define that all added angles should be about zero or (2 * PI) */
        float angletot = 0.0;
        float fp1[2], fp2[2];
        unsigned int i;
        const float *p1, *p2;
    
        p1 = verts[nr - 1];
    
        /* first vector */
        fp1[0] = p1[0] - pt[0];
        fp1[1] = p1[1] - pt[1];
    
        for (i = 0; i < nr; i++) {
            p2 = verts[i];
    
            /* second vector */
            fp2[0] = p2[0] - pt[0];
            fp2[1] = p2[1] - pt[1];
    
            /* dot and angle and cross */
            angletot += angle_signed_v2v2(fp1, fp2);
    
            /* circulate */
            copy_v2_v2(fp1, fp2);
            p1 = p2;
        }
    
        angletot = fabsf(angletot);
        if (use_holes) {
            const float nested = floorf((angletot / (float)(M_PI * 2.0)) + 0.00001f);
            angletot -= nested * (float)(M_PI * 2.0);
            return (angletot > 4.0f) != ((int)nested % 2);
        }
        else {
            return (angletot > 4.0f);
        }
    }
    
    /* math lib */
    
    static float dot_v2v2(const float a[2], const float b[2])
    {
        return a[0] * b[0] + a[1] * b[1];
    }
    
    static float angle_signed_v2v2(const float v1[2], const float v2[2])
    {
        const float perp_dot = (v1[1] * v2[0]) - (v1[0] * v2[1]);
        return atan2f(perp_dot, dot_v2v2(v1, v2));
    }
    
    static void copy_v2_v2(float r[2], const float a[2])
    {
        r[0] = a[0];
        r[1] = a[1];
    }
    

    Note: this is one of the less optimal methods since it includes a lot of calls to atan2f, but it may be of interest to developers reading this thread (in my tests its ~23x slower then using the line intersection method).

    0 讨论(0)
  • 2020-11-21 05:52

    When using qt (Qt 4.3+), one can use QPolygon's function containsPoint

    0 讨论(0)
  • 2020-11-21 05:53

    The Eric Haines article cited by bobobobo is really excellent. Particularly interesting are the tables comparing performance of the algorithms; the angle summation method is really bad compared to the others. Also interesting is that optimisations like using a lookup grid to further subdivide the polygon into "in" and "out" sectors can make the test incredibly fast even on polygons with > 1000 sides.

    Anyway, it's early days but my vote goes to the "crossings" method, which is pretty much what Mecki describes I think. However I found it most succintly described and codified by David Bourke. I love that there is no real trigonometry required, and it works for convex and concave, and it performs reasonably well as the number of sides increases.

    By the way, here's one of the performance tables from the Eric Haines' article for interest, testing on random polygons.

                           number of edges per polygon
                             3       4      10      100    1000
    MacMartin               2.9     3.2     5.9     50.6    485
    Crossings               3.1     3.4     6.8     60.0    624
    Triangle Fan+edge sort  1.1     1.8     6.5     77.6    787
    Triangle Fan            1.2     2.1     7.3     85.4    865
    Barycentric             2.1     3.8    13.8    160.7   1665
    Angle Summation        56.2    70.4   153.6   1403.8  14693
    
    Grid (100x100)          1.5     1.5     1.6      2.1      9.8
    Grid (20x20)            1.7     1.7     1.9      5.7     42.2
    Bins (100)              1.8     1.9     2.7     15.1    117
    Bins (20)               2.1     2.2     3.7     26.3    278
    
    0 讨论(0)
  • 2020-11-21 05:55

    I think the following piece of code is the best solution (taken from here):

    int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
    {
      int i, j, c = 0;
      for (i = 0, j = nvert-1; i < nvert; j = i++) {
        if ( ((verty[i]>testy) != (verty[j]>testy)) &&
         (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
           c = !c;
      }
      return c;
    }
    

    Arguments

    • nvert: Number of vertices in the polygon. Whether to repeat the first vertex at the end has been discussed in the article referred above.
    • vertx, verty: Arrays containing the x- and y-coordinates of the polygon's vertices.
    • testx, testy: X- and y-coordinate of the test point.

    It's both short and efficient and works both for convex and concave polygons. As suggested before, you should check the bounding rectangle first and treat polygon holes separately.

    The idea behind this is pretty simple. The author describes it as follows:

    I run a semi-infinite ray horizontally (increasing x, fixed y) out from the test point, and count how many edges it crosses. At each crossing, the ray switches between inside and outside. This is called the Jordan curve theorem.

    The variable c is switching from 0 to 1 and 1 to 0 each time the horizontal ray crosses any edge. So basically it's keeping track of whether the number of edges crossed are even or odd. 0 means even and 1 means odd.

    0 讨论(0)
  • 2020-11-21 05:57

    Swift version of the answer by nirg:

    extension CGPoint {
        func isInsidePolygon(vertices: [CGPoint]) -> Bool {
            guard !vertices.isEmpty else { return false }
            var j = vertices.last!, c = false
            for i in vertices {
                let a = (i.y > y) != (j.y > y)
                let b = (x < (j.x - i.x) * (y - i.y) / (j.y - i.y) + i.x)
                if a && b { c = !c }
                j = i
            }
            return c
        }
    }
    
    0 讨论(0)
  • 2020-11-21 05:57

    .Net port:

        static void Main(string[] args)
        {
    
            Console.Write("Hola");
            List<double> vertx = new List<double>();
            List<double> verty = new List<double>();
    
            int i, j, c = 0;
    
            vertx.Add(1);
            vertx.Add(2);
            vertx.Add(1);
            vertx.Add(4);
            vertx.Add(4);
            vertx.Add(1);
    
            verty.Add(1);
            verty.Add(2);
            verty.Add(4);
            verty.Add(4);
            verty.Add(1);
            verty.Add(1);
    
            int nvert = 6;  //Vértices del poligono
    
            double testx = 2;
            double testy = 5;
    
    
            for (i = 0, j = nvert - 1; i < nvert; j = i++)
            {
                if (((verty[i] > testy) != (verty[j] > testy)) &&
                 (testx < (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + vertx[i]))
                    c = 1;
            }
        }
    
    0 讨论(0)
提交回复
热议问题