Testing whether or not a point is within a polygon.
|
|
Ephyu Reino
Registered User
Join date: 18 Jan 2007
Posts: 16
|
09-16-2007 16:16
I have an object that needs to know whether or not it's within a set of boundaries defined by regional position vectors. I'm only concerned with the X and Y axes, not Z. My code thus far is this, adapted from a C++ code example I found on the subject: The defined variables are arbitrary, just the ones I was using for testing when I encountered problems. From: My Code // Variables to be used for testing. list xpoints = [80.392,80.392,82.500,85.000,85.000]; list ypoints = [28.286,32.500,34.500,32.500,28.286]; integer points = 5; // The function itself. integer inPoly(integer num_points, list xp, list yp, vector point) { integer i; integer j; integer inside = FALSE; for (i = 0, j = num_points; i < num_points; j = i++) { if ( ( ((llList2Float(ypoints,i)<=point.y) && (point.y<llList2Float(ypoints,j))) || ((llList2Float(ypoints,j)<=point.y) && (point.y<llList2Float(ypoints,i))) ) && (point.x < (llList2Float(xpoints,j) - llList2Float(xpoints,i)) * (point.y - llList2Float(ypoints,i)) / (llList2Float(ypoints,j) - llList2Float(ypoints,i)) + llList2Float(xpoints,i))) inside = TRUE; } return inside; } default { touch_start(integer detected){ vector test = llGetPos(); integer inside = inPoly(points, xpoints, ypoints, test); if(inside) llOwnerSay("Point is inside."  ; else llOwnerSay("Point is outside."  ; } } Now, with the code in this state, I get: [15:51] Object: Script run-time error [15:51] Object: Math Error By changing: for (i = 0, j = num_points-1; i < num_points; j = i++) to for (i = 0, j = num_points; i < num_points; j = i++) lets the script compile, and it works fairly well, with just one problem. Testing anywhere inside the defined shape (which I have staked out with prims) says "inside." Testing anywhere over the boundaries formed by p2-p3, p3-p4, p4-p5, or p5-p1 says "outside." However, testing over the line between p1-p2, says "inside." Horrible MS-Paint drawing illustrating this: http://i6.photobucket.com/albums/y246/angstiuslupus/sl_shit/wtf.pngIn that image, the dots represent tests I've done, or their approximations. Why are tests in the gray area at the top returning "inside"?
|
|
Ephyu Reino
Registered User
Join date: 18 Jan 2007
Posts: 16
|
09-16-2007 17:24
In case it adds anything, here's the source code I used.. could there be a problem in my translation to LSL?
#include <iostream.h> #include <math.h> #include <stdlib.h>
// // Input parameters: // npol : number of vertices // xp[npol], yp[npol] : x,y coord of vertices // x,y : coord of point to test // // Return Value: // 0 : test point is outside polygon // 1 : test point is inside polygon // // Notes: // if test point is on the border, 0 or 1 is returned. // If there exists an adiacent polygon, the point is // _in_ only in one of the two. //
int pnpoly(int npol, float *xp, float *yp, float x, float y) { int i, j, c = 0; for (i = 0, j = npol-1; i < npol; j = i++) {
if ( ( ((yp<=y) && (y<yp[j])) || ((yp[j]<=y) && (y<yp)) ) && (x < (xp[j] - xp) * (y - yp) / (yp[j] - yp) + xp)) c = !c; } return c; }
|
|
Qie Niangao
Coin-operated
Join date: 24 May 2006
Posts: 7,138
|
09-16-2007 18:37
Looks like division by zero when yp[j] == yp ... should have the same problem in the C code. [edit: although order of evaluation of the conditionals is different... perhaps something in the first clause of the && prevents that condition from every getting tested... need to check that.] [edit 2: yeah, I think that was it... the following version of the inPoly function tries to make it more explicit... hopefully works as intended:
integer inPoly(integer num_points, list xp, list yp, vector point) { integer i=0; integer j; integer inside = FALSE; for (j = num_points-1; i < num_points; j = i++) { if (((llList2Float(ypoints,i)<=point.y) && (point.y<llList2Float(ypoints,j))) || ((llList2Float(ypoints,j)<=point.y) && (point.y<llList2Float(ypoints,i))) ) if (point.x < (llList2Float(xpoints,j) - llList2Float(xpoints,i)) * (point.y - llList2Float(ypoints,i)) / (llList2Float(ypoints,j) - llList2Float(ypoints,i)) + llList2Float(xpoints,i) ) inside = (! inside); //Qie: was TRUE; } return inside; }
Note that I also changed the logic of assignment of "inside" to swap sense, as it was in the C code. I've no idea if that's right, but it's closer to the original code.]
|
|
Ephyu Reino
Registered User
Join date: 18 Jan 2007
Posts: 16
|
09-16-2007 19:07
Thank you very much. Not quite sure I even understand 100% what the changes you made are, but they fixed it. It works exactly as intended now.
|
|
Hippyjim Starbrook
Greenpeace Activist
Join date: 15 Dec 2006
Posts: 24
|
03-22-2009 11:11
Hi - has anyone got this to work? I'm trying to use it to replicate HTML image maps in LSL, but I'm getting stuck at the first hurdle. As a test, I assumed a total size of 100 x 100. I defined a rectangle "hotspot" with corners at x30 y30, x30 y60, x60 y30, x60 y60. That's basically a rectangle that's 30 wide & tall, bottom left corner at x30y30. Using this function, inPoly(4, [60, 30, 60, 30], [60, 60, 30, 30], <32, 36, 0>  ; returns false Am I going wrong somewhere?
|
|
Nexii Malthus
[Cubitar]Mothership
Join date: 24 Apr 2006
Posts: 400
|
03-23-2009 04:17
I think the order you give the vertices might be important here, as reversing a polygon is pretty much like creating a no-go zone in the middle but instead covering the outside. For making things simple, I'd recommend using the geometric libraries' version of the inPoly function. gCPXx( [<30,30,0>,<60,30,0>,<60,60,0>,<30,60,0>], <32, 36, 0>  ; integer gCPXx( list CP, vector X ) {//Copyright (c) 1970-2003, Wm. Randolph Franklin; 2008, Strife Onizuka integer i = ~(CP != []); integer c = 0; if(i < -2){ vector vi = llList2Vector(CP, -1); do { vector vj = vi; vi = llList2Vector(CP, i); if((vi.y > X.y) ^ (vj.y > X.y)){ if(vj.y != vi.y) c = c ^ (X.x < (((vj.x - vi.x) * (X.y - vi.y) / (vj.y - vi.y)) + vi.x)); else c = c ^ (0 < ((vj.x-vi.x) * (X.y-vi.y))); } } while (++i); } return c; }
_____________________
 Geometric Library, for all your 3D maths needs. https://wiki.secondlife.com/wiki/Geometric Creator of the Vertical Life Client
|
|
Innula Zenovka
Registered User
Join date: 20 Jun 2007
Posts: 1,825
|
03-23-2009 08:34
Thank you so much! I've been wondering how to get this to work, too. I know, I fear, very little about the conventions of vector geometry; experiments seem to indicate that this function expects the vectors to start at the bottom left of the hot area and to run anti-clockwise. At least that's what seemed to work with a texture comprising four squares I've just made in gimp and tested out llDetectedTouchUV on -- bottom left, bottom right, top right, top left. Where may I find a really really simple guide to this, so I can learn how to use more complex shapes?
|
|
Hippyjim Starbrook
Greenpeace Activist
Join date: 15 Dec 2006
Posts: 24
|
03-23-2009 16:22
Thanks for the response Nexii (and right there withya Innula) - tbh i spotted that one on the wiki, and tried it with no success - eg: gCPXx([<30,30,0>,<60,30,0>,<60,60,0>,<30,60,0>], <14, 20, 0>  ; returns true. I'm starting to wonder if it's just me!
|
|
Innula Zenovka
Registered User
Join date: 20 Jun 2007
Posts: 1,825
|
03-24-2009 04:29
I've got the example working with this list hotspot = [<0.3,0.3,0.0>, <0.6,0.3,0.0>, <0.6,0.6,0.0>, <0.3,0.6,0.0>]; default { state_entry() { llSetTexture("730687e3-775f-a465-e535-50df8e0a34ac", 0); } touch_start(integer total_number) { vector UV = llDetectedTouchUV(0); float U = UV.x; float V = UV.y; llOwnerSay("U -- x -- is "+ (string)U+" and V -- y -- is "+(string)V); if(gCPXx(hotspot,UV)){ llOwnerSay("in"); } else { llOwnerSay("out"); } } }
|