HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

IsPointInPoly

02-03-2010, 04:34 AM#1
Ammorth
IsPointInPoly - By Ammorth

Requires LinkedList

The script can handle both simple and complex polygons.

Although this is a script instead of a system, I have included a test map to easily test the function. It can be removed if this is approved.

Update: Point is now private, to remove any conflicts with other systems.

Collapse JASS:
// ============================================================================
// IsPointInPoly - By Ammorth                                    Feb 13, 2010
//   **Requires JassHelper (vJass) to compile**
//   Requires LinkedList (by Ammorth, at wc3c)
//
//   IsPointInPoly is based on code wrote in C which I found at:
//   [url]http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html[/url]
//
// What does it do:
//     As the name implies, IsPointInPoly will return true if a point is inside
//     a Polygon and false if it is not.
//
// Notes: 
//       This library introduces two "new" objects to vJass, Point and Polygon.
//       Point is a simple real x and real y while Polygon is a list of Points.
//       Point is managed automatically by the Polygon but a Polygon must be created
//       to pass to the function.
//
//       Point is now private to prevent conflicts with other libraries.  This will not
//       change the workings of this script, as Point was never directly used.
//
//       A Polygon is a 2D shape defined by at least 3 vertices.  Therefore, the
//       the Polygon passed must also have at least 3 points or the test will
//       return false and a message will be printed in debug mode.
//
// Creating A Polygon:
//     Polygon.create() -> Polygon
//         use Polygon.create() to create a Polygon.  Make sure to store the 
//         Polygon to a variable, as you will need access to add points
//
//     YourPolygon.addPoint(real x, real y) -> nothing
//         Add points with Polygon.addPoint(x, y).  You can add as many points
//         as you want, but must add the points in order.  Although the order
//         matters, the direction (CCW or CC) does no matter
//
// Checking a Polygon for a point:
//
//     IsPointInPoly(takes real x, real y, Polygon p) -> boolean
//         This function will check if the given point is within the Polygon.
//         It will return a boolean that you can use directly in an if statement
//         or store in a boolean variable.
//
// Cleaning up Polygon:
//
//     Polygon.destroy() -> nothing
//         Use Polygon.destroy() to remove the Polygon and its points from
//         memory.  If you have a static polygon (does not change points)
//         you can store the Polygon in a global and use it multiple times
//         for various points.
//         
// If you have any further questions, consult the TestPoly scope, specifically
// the TimerLoop function.
//
// =============================================================================
library IsPointInPoly requires LinkedList

    private struct Point
        real x
        real y
        
        static method create takes real x, real y returns thistype
            local thistype this = thistype.allocate()
            set .x = x
            set .y = y
            return this
        endmethod
    endstruct

    // anything to do with .size is only for a quick debug check in debug mode
    // it is not required for the script to run and is therefore removed when 
    // not saved in debug mode.
    struct Polygon
        List points
        debug integer size
        
        static method create takes nothing returns thistype
            local thistype this = thistype.allocate()
            debug set .size = 0
            set .points = List.create()
            return this
        endmethod
        
        method addPoint takes real x, real y returns nothing
            local Point p = Point.create(x, y)
            call Link.createLast(.points, p)
            debug set .size = .size + 1
        endmethod
        
        method onDestroy takes nothing returns nothing
            local Link temp = .points.first
            loop
                exitwhen temp == 0
                call Point(temp.data).destroy()
                set temp = temp.next
            endloop
            call .points.destroy()
        endmethod
    endstruct
    
    function IsPointInPoly takes real x, real y, Polygon p returns boolean
        local boolean b = false
        local Link l = p.points.first
        local Point i = l.data
        local Point j = p.points.last.data
        debug if p.size < 2 then // polygon must contain at least 3 points
            debug call BJDebugMsg("IsPointInPoly Error: The Polygon must have at least 3 points!")
            debug return false
        debug endif
        loop
            exitwhen l == 0
            if ( ((i.y > y) != (j.y > y)) and (x < (j.x-i.x) * (y-i.y) / (j.y-i.y) + i.x) ) then
                set b = not b
            endif
            set j = i
            set l = l.next
            set i = l.data
        endloop
        return b
    endfunction

/* Below, I have pasted the original C code found at [url]http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html[/url]
I have used this function and the rest of the information on the page to convert his function to vJass
I wouldn't be surprised if cJass could compile the body directly, but I did not test it as I am more used to vJass.

Author: W. Randolph Franklin

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;
}*/

endlibrary
Attached Files
File type: w3xIsPointInPoly.w3x (27.5 KB)
02-03-2010, 09:51 AM#2
Tot
whoo...
I did the same, but with a lot of more lines...
and this works?

I know that a point is inside a polygon/shape, when an axis through his x and y coordinate intersect with an uneven number of polygon-lines at each side, but how is yours working?
02-03-2010, 10:40 AM#3
Anachron
Learn2Read please.

Collapse JASS:
loop
            exitwhen l == 0
            if ( ((i.y > y) != (j.y > y)) and (x < (j.x-i.x) * (y-i.y) / (j.y-i.y) + i.x) ) then
                set b = not b
            endif
            set j = i
            set l = l.next
            set i = l.data
        endloop
        return b
he checks whether the Y is smaller as two comparement values and whether X is inside x1 and x2. He does that for the whole loop.
02-03-2010, 02:23 PM#4
Tot
Quote:
Originally Posted by Anachron
Learn2Read please.

Collapse JASS:
loop
            exitwhen l == 0
            if ( ((i.y > y) != (j.y > y)) and (x < (j.x-i.x) * (y-i.y) / (j.y-i.y) + i.x) ) then
                set b = not b
            endif
            set j = i
            set l = l.next
            set i = l.data
        endloop
        return b
he checks whether the Y is smaller as two comparement values and whether X is inside x1 and x2. He does that for the whole loop.

yea...I'm fully able to read and write, but I'm not able to understand his way of checking...
he's checking it via rectangles, but what I'm interested in is weather it's fully fail-secure or not (e.g. for very big polygons (only size, not number of corners) and a near, but outside point)
02-03-2010, 03:48 PM#5
Rising_Dusk
Quote:
Originally Posted by Anachron
Learn2Read please.
Don't be a douchebag; the algorithm used here may not be intuitive for some people.
02-03-2010, 03:52 PM#6
Anachron
Hmm, sorry 'bout that, if that was to hard. But even then, he could just check some explainations.
Or even this.
02-03-2010, 04:21 PM#7
Ammorth
Quote:
Originally Posted by Tot
yea...I'm fully able to read and write, but I'm not able to understand his way of checking...
he's checking it via rectangles, but what I'm interested in is weather it's fully fail-secure or not (e.g. for very big polygons (only size, not number of corners) and a near, but outside point)

It uses a theoretical ray with constant y and increasing x to test the points (horizontal line). Each line is defined by 2 points (i and j) in the script. We start with j being the last point and i being the first. Each loop then sets j = i++ (i increases and j is always 1 less than i). We will therefore cover every line in the polygon.

Now, for each line we do a couple of checks. The fist part of the check:

Code:
(y1 > y) != (y2 > y)

makes sure the ray could actually intersect the line (that it is within the y range). This also prevents a possible divide by 0 in the second bit, as like C, vJass will not evalute the true in a "false and true" statement.

For the second part of the if, we use the following inequality to check which side of the line we are on:

Code:
(y - y0) (x1 - x0) - (x - x0) (y1 - y0)

This will return positive values when the point is to the left, zero when it is on the line and negative values when the point is to the right. Since we use an increasing x, we only care for lines that lie to the right of the test point (therefore the point lies to the left of the lines). Our inequality is then:

Code:
(y - y0) (x1 - x0) - (x - x0) (y1 - y0) > 0

Moving the inequality around, we come up with:

Code:
(y - y0) (x1 - x0) / (y1 - y0) + x0 > x

So, if the first check is true and the point is within the y's of our line, and then it also lies to the left of the line, we will intersect the line with our ray that has a constant y and increasing x.

Now we count the number of intersections, and if it is odd, we are inside; even, we are outside.

As for special cases (you intersect a vertex), these are handled by the first check:

Code:
(y1 > y) != (y2 > y)

Because of the inequalities, we ignore lines that we intersect at the highest point. Example:

Code:
y = y1
y > y2

( y1 > y ) != ( y2 > y )
( false  ) != ( false  )
( false                )

where as a line intersecting at the lowest point:

Code:
y < y1
y = y2

( y1 > y ) != ( y2 > y )
( true   ) != ( false  )
( true                 )

This way we will only ever intersect with 1 point at a vertex. If you noticed in the test map, when the units are created, the test unit is lying on a vertex and still is counted as being inside. The vertex is only counted once and therefore works as intended.

And just to note again, I was not the original creator of this testing method, I merely converted the test to vJass.
02-04-2010, 11:44 AM#8
Tot
now i get it!!
it's the same method I used, only inlined and less complex...
02-04-2010, 01:30 PM#9
Anitarf
Quote:
Originally Posted by Ammorth
Now we count the number of intersections, and if it is odd, we are outside; even, we are inside.
Shouldn't that be the other way around?

Quote:
As for special cases (you intersect a vertex), these are handled by the first check:

Code:
(y1 > y) != (y2 > y)

Because of the inequalities, we ignore lines that we intersect at the highest point. Example:

Code:
y = y1
y > y2

( y1 > y ) != ( y2 > y )
( false  ) != ( false  )
( false                )

where as a line intersecting at the lowest point:

Code:
y < y1
y = y2

( y1 > y ) != ( y2 > y )
( true   ) != ( false  )
( true                 )

This way we will only ever intersect with 1 point at a vertex. If you noticed in the test map, when the units are created, the test unit is lying on a vertex and still is counted as being inside. The vertex is only counted once and therefore works as intended.
What about if the vertex is the "highest" point in both lines? What about if y1 equals y2 (I suppose in this second case the line is simply ignored without any problems, but in the first case both lines are ignored which can't be good if the unit is on the vertex)?
02-04-2010, 06:40 PM#10
Ammorth
Quote:
Originally Posted by Anitarf
Shouldn't that be the other way around?

Ya, you are right. I was trying to post quickly and never reviewed it.


Quote:
Originally Posted by Anitarf
What about if the vertex is the "highest" point in both lines? What about if y1 equals y2 (I suppose in this second case the line is simply ignored without any problems, but in the first case both lines are ignored which can't be good if the unit is on the vertex)?

Well the code is to check if a unit lies within the polygon and not on the edge. That being said, points on the edges will return true or false (due to rounding, edges, etc), but the same point will always return the same value. It would be possible to write a script to check edges and then OR the two scripts if you want to include edges in the check. I needed something to quickly determine if a point was inside a polygon and this does just that. Hell, even rects have issues with points that lie on the edges.

I'm not saying your point isn't valid, im just saying edges are sort of a gray area when it comes to point inside detection.
02-13-2010, 10:59 PM#11
Ammorth
Quick update, point is now decleared private, so other librarys will not conflict with structs.
03-28-2010, 09:45 AM#12
Anitarf
I was thinking that it would be useful to have support for dynamic polygons, since creating&destroying a poly for each check seems costly compared to the doing the check itself. The Point would need to be public (you would need to rename it to something like PolygonNode) so that users could then move it around. Of course, you would loose some encapsulation that way, since users could do stupid things with the nodes, so this may not be the best idea.

An alternative would be to keep the point private, but add another method to the Polygon struct, "addUnitPoint" or something like that. With this, you could lock one node of the polyon to a unit and then move that around.

Just throwing some suggestions out there, the script is still fine as it is so if you do not think doing anything of the above is a good idea I can approve it anyway.
03-28-2010, 05:29 PM#13
Ammorth
Moving points within the polygon would be more effecient than creating and destroying on each iteration. For static polygons though, this script excels.

I think the best appoach, if it is really needed, would be to change this script into a "Polygon" script, which handles polygons and anything to do with polygons (creation, destruction, adding point, moving points, removing points, isInside, isEdge, onEnter, onExit, Enums, etc...). The only downside to this is that the library will increase in size drastically and do people really need all that functionality?
03-28-2010, 05:57 PM#14
Anitarf
You are right, if the need arises you can always expand the library later, until then this is perfectly sufficient. Approved.
08-29-2010, 01:40 PM#15
iNfamous
Hey Ammorth!
How could i implement a simple "GetRandomPointInPolygon" function?
Loop getting a random point in the whole map and checking if its inside sounds a bit stupid :)

€dit: I finally managed to create my own GetRandomPinPoly function so this is solved.
I create an outer boundary of the polygon, and then loop trough random points in that boundary til they are inside.