| 02-03-2010, 04:34 AM | #1 |
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. 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 |
| 02-03-2010, 09:51 AM | #2 |
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 |
Learn2Read please. 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 |
| 02-03-2010, 02:23 PM | #4 | |
Quote:
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 | |
Quote:
|
| 02-03-2010, 03:52 PM | #6 |
| 02-03-2010, 04:21 PM | #7 | |
Quote:
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 |
now i get it!! it's the same method I used, only inlined and less complex... |
| 02-04-2010, 01:30 PM | #9 | ||
Quote:
Quote:
|
| 02-04-2010, 06:40 PM | #10 | ||
Quote:
Ya, you are right. I was trying to post quickly and never reviewed it. Quote:
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 |
Quick update, point is now decleared private, so other librarys will not conflict with structs. |
| 03-28-2010, 09:45 AM | #12 |
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 |
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 |
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 |
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. |
