Skip to content

Commit eff23c6

Browse files
Homer Reidstevengj
authored andcommitted
refine point_in_prism to handle points on prism faces more carefully (#23)
1 parent e871991 commit eff23c6

2 files changed

Lines changed: 111 additions & 32 deletions

File tree

utils/geom.c

Lines changed: 93 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2127,14 +2127,30 @@ void get_prism_bounding_box(prism *prsm, geom_box *box)
21272127
}
21282128

21292129
/***************************************************************/
2130-
/* find the value of s at which the line p+s*d intersects the */
2131-
/* line segment connecting v1 to v2 (in 2 dimensions) */
2130+
/* given 2D points p,v1,v2 and a 2D direction vector d, */
2131+
/* determine whether or not the line p + s*d intersects the */
2132+
/* line segment v1--v2. */
21322133
/* algorithm: solve the 2x2 linear system p+s*d = a+t*b */
21332134
/* where s,t are scalars and p,d,a,b are 2-vectors with */
2134-
/* a=v1, b=v2-v1 */
2135+
/* a=v1, b=v2-v1. intersection then corresponds to 0 <= t < 1. */
2136+
/* return values: */
2137+
/* ** case 1: d is not parallel to v1--v2 ** */
2138+
/* NON_INTERSECTING test negative */
2139+
/* INTERSECTING test positive */
2140+
/* ** case 2: d is parallel to v1--v2 ** */
2141+
/* IN_SEGMENT p lies on line segment v1--v2 */
2142+
/* ON_RAY p does not lie on v1--v2, but there is a */
2143+
/* *positive* value of s such that p+s*d */
2144+
/* lies on v1--v2 */
2145+
/* NON_INTERSECTING neither of the above */
21352146
/***************************************************************/
2136-
boolean intersect_line_with_segment(double px, double py, double dx, double dy,
2137-
vector3 v1, vector3 v2, double *s)
2147+
#define THRESH 1.0e-5
2148+
#define NON_INTERSECTING 0
2149+
#define INTERSECTING 1
2150+
#define IN_SEGMENT 2
2151+
#define ON_RAY 3
2152+
int intersect_line_with_segment(double px, double py, double dx, double dy,
2153+
vector3 v1, vector3 v2, double *s)
21382154
{
21392155
double ax = v1.x, ay = v1.y;
21402156
double bx = v2.x-v1.x, by = v2.y-v1.y;
@@ -2143,17 +2159,32 @@ boolean intersect_line_with_segment(double px, double py, double dx, double dy,
21432159
double RHSx = ax - px, RHSy = ay - py;
21442160
double DetM = M00*M11 - M01*M10;
21452161
double L2 = bx*bx + by*by; // squared length of edge
2146-
if ( fabs(DetM) < 1.0e-10*L2 ) // d zero or nearly parallel to edge-->no intersection
2147-
return 0;
2162+
if ( fabs(DetM) < 1.0e-10*L2 )
2163+
{ // d zero or nearly parallel to edge
2164+
double pmv1x = px-v1.x, pmv1y = py-v1.y, npmv1 = sqrt(pmv1x*pmv1x + pmv1y*pmv1y);
2165+
double pmv2x = px-v2.x, pmv2y = py-v2.y, npmv2 = sqrt(pmv2x*pmv2x + pmv2y*pmv2y);
2166+
double dot = pmv1x*pmv2x + pmv1y*pmv2y;
2167+
if ( fabs(dot) < (1.0-THRESH)*npmv1*npmv2 )
2168+
return NON_INTERSECTING;
2169+
else if (dot<0.0)
2170+
{ *s=0.0;
2171+
return IN_SEGMENT;
2172+
}
2173+
else if ( (dx*pmv1x + dy*pmv1y) < 0.0 )
2174+
{ *s = fmin(npmv1, npmv2) / sqrt(dx*dx + dy*dy);
2175+
return ON_RAY;
2176+
}
2177+
return NON_INTERSECTING;
2178+
}
21482179

2149-
double t = (M00*RHSy-M10*RHSx)/DetM;
2180+
float t = (M00*RHSy-M10*RHSx)/DetM;
21502181
if (s) *s = (M11*RHSx-M01*RHSy)/DetM;
21512182

21522183
// the plumb line intersects the segment if 0<=t<=1, with t==0,1
21532184
// corresponding to the endpoints; for our purposes we count
21542185
// the intersection if the plumb line runs through the t==0 vertex, but
21552186
// NOT the t==1 vertex, to avoid double-counting for complete polygons.
2156-
return t < 0 || ((float)t) >= 1 ? 0 : 1;
2187+
return ( t<-THRESH || t>=(1-THRESH) ) ? NON_INTERSECTING : INTERSECTING;
21572188
}
21582189

21592190
// like the previous routine, but only count intersections if s>=0
@@ -2162,10 +2193,10 @@ boolean intersect_ray_with_segment(double px, double py, double dx, double dy,
21622193
{
21632194
double ss;
21642195
int status=intersect_line_with_segment(px,py,dx,dy,v1,v2,&ss);
2165-
if (status==0 || ss<0.0)
2166-
return 0;
2196+
if (status==INTERSECTING && ss<0.0)
2197+
return NON_INTERSECTING;
21672198
if (s) *s=ss;
2168-
return 1;
2199+
return status;
21692200
}
21702201

21712202
/***************************************************************/
@@ -2175,33 +2206,67 @@ boolean intersect_ray_with_segment(double px, double py, double dx, double dy,
21752206
/* p to infinity and count the number of edges intersected; */
21762207
/* point lies in polygon iff this is number is odd. */
21772208
/***************************************************************/
2178-
boolean point_in_polygon(double px, double py, vector3 *vertices, int num_vertices)
2209+
boolean point_in_or_on_polygon(double px, double py,
2210+
vector3 *vertices, int num_vertices,
2211+
boolean include_boundaries)
21792212
{
21802213
double dx=0.0, dy=-1.0;
2181-
int num_side_intersections=0;
2182-
int nv;
2214+
int nv, edges_crossed=0;
21832215
for(nv=0; nv<num_vertices; nv++)
2184-
num_side_intersections
2185-
+=intersect_ray_with_segment(px, py, dx, dy,
2186-
vertices[nv], vertices[(nv+1)%num_vertices],
2187-
0);
2188-
return num_side_intersections%2;
2216+
{ int status = intersect_ray_with_segment(px, py, dx, dy,
2217+
vertices[nv], vertices[(nv+1)%num_vertices], 0);
2218+
if (status==IN_SEGMENT)
2219+
return include_boundaries;
2220+
else if (status==INTERSECTING)
2221+
edges_crossed++;
2222+
else if (status==ON_RAY)
2223+
{ vector3 vm1 = vertices[ (nv==0 ? num_vertices-1 : nv-1) ];
2224+
vector3 v = vertices[ nv ];
2225+
vector3 vp1 = vertices[ (nv+1) % num_vertices ];
2226+
vector3 vp2 = vertices[ (nv+2) % num_vertices ];
2227+
int last_status = intersect_ray_with_segment(px, py, dx, dy, vm1, v, 0);
2228+
if (last_status==INTERSECTING) edges_crossed--;
2229+
int next_status = intersect_ray_with_segment(px, py, dx, dy, vp1, vp2, 0);
2230+
if (next_status==INTERSECTING) edges_crossed--;
2231+
}
2232+
}
2233+
return (edges_crossed % 2);
21892234
}
21902235

2236+
boolean point_in_polygon(double px, double py,
2237+
vector3 *vertices, int num_vertices)
2238+
{ return point_in_or_on_polygon(px, py, vertices, num_vertices, 1); }
2239+
21912240
/***************************************************************/
21922241
/* return 1 or 0 if xc lies inside or outside the prism */
21932242
/***************************************************************/
2194-
boolean point_in_prism(prism *prsm, vector3 xc)
2243+
boolean point_in_or_on_prism(prism *prsm, vector3 xc, boolean include_boundaries)
21952244
{
21962245
vector3 *vertices = prsm->vertices.items;
21972246
int num_vertices = prsm->vertices.num_items;
21982247
double height = prsm->height;
21992248
vector3 xp = prism_coordinate_c2p(prsm, xc);
22002249
if ( xp.z<0.0 || xp.z>prsm->height)
22012250
return 0;
2202-
return point_in_polygon(xp.x, xp.y, vertices, num_vertices);
2251+
return point_in_or_on_polygon(xp.x, xp.y, vertices, num_vertices, include_boundaries);
22032252
}
22042253

2254+
boolean point_in_prism(prism *prsm, vector3 xc)
2255+
{
2256+
// by default, points on polygon edges are considered to lie inside the
2257+
// polygon; this can be reversed by setting the environment variable
2258+
// LIBCTL_EXCLUDE_BOUNDARIES=1
2259+
static boolean include_boundaries=1, init=0;
2260+
if (init==0)
2261+
{ init=1;
2262+
char *s=getenv("LIBCTL_EXCLUDE_BOUNDARIES");
2263+
if (s && s[0]=='1') include_boundaries=0;
2264+
}
2265+
2266+
return point_in_or_on_prism(prsm, xc, include_boundaries);
2267+
}
2268+
2269+
22052270
// comparator for qsort
22062271
static int dcmp(const void *pd1, const void *pd2)
22072272
{ double d1=*((double *)pd1), d2=*((double *)pd2);
@@ -2242,9 +2307,9 @@ int intersect_line_with_prism(prism *prsm, vector3 p, vector3 d, double *slist)
22422307
// intersection of the XY-plane projection of the line with the
22432308
// polygon edge between vertices (nv,nv+1).
22442309
double s;
2245-
if (!intersect_line_with_segment(p_prsm.x, p_prsm.y, d_prsm.x, d_prsm.y,
2246-
vertices[nv], vertices[nvp1], &s)
2247-
) continue;
2310+
int status=intersect_line_with_segment(p_prsm.x, p_prsm.y, d_prsm.x, d_prsm.y,
2311+
vertices[nv], vertices[nvp1], &s);
2312+
if (status==NON_INTERSECTING || status==ON_RAY) continue;
22482313

22492314
// OK, we know the XY-plane projection of the line intersects the polygon edge;
22502315
// now go back to 3D, ask for the z-coordinate at which the line intersects
@@ -2397,6 +2462,8 @@ double min_distance_to_prism_roof_or_ceiling(vector3 p,
23972462
return sqrt(s*s+d*d);
23982463
}
23992464

2465+
/* utility routines for writing points, lines, quadrilaterals */
2466+
/* to text files for viewing in e.g. gnuplot */
24002467
void GPPoint(FILE *f, vector3 v, prism *prsm)
24012468
{ if (prsm)
24022469
v = prism_coordinate_p2c(prsm, v);
@@ -2492,8 +2559,7 @@ void display_prism_info(int indentby, prism *prsm)
24922559
for(nv=0; nv<num_vertices; nv++)
24932560
{ vector3 v = matrix3x3_vector3_mult(m_p2c, vertices[nv]);
24942561
printf("%*s {%e,%e,%e}\n",indentby,"",v.x,v.y,v.z);
2495-
};
2496-
2562+
}
24972563
}
24982564

24992565
/***************************************************************/

utils/test-prism.c

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
vector3 normal_to_plane(vector3 o, vector3 v1, vector3 v2, vector3 p, double *min_distance);
3535
double min_distance_to_line_segment(vector3 p, vector3 v1, vector3 v2);
36+
boolean point_in_or_on_prism(prism *prsm, vector3 xc, boolean include_boundaries);
3637

3738
#define K_PI 3.141592653589793238462643383279502884197
3839

@@ -235,20 +236,32 @@ int test_point_inclusion(geometric_object the_block, geometric_object the_prism,
235236
vector3 min_corner = vector3_scale(-1.0, size);
236237
vector3 max_corner = vector3_scale(+1.0, size);
237238
FILE *f = write_log ? fopen("/tmp/test-prism.points","w") : 0;
238-
int num_failed=0;
239-
int n;
239+
int num_failed=0, num_adjusted=0, n;
240240
for(n=0; n<num_tests; n++)
241241
{
242242
vector3 p = random_point_in_box(min_corner, max_corner);
243243
boolean in_block=point_in_objectp(p,the_block);
244244
boolean in_prism=point_in_objectp(p,the_prism);
245+
245246
if (in_block!=in_prism)
246-
num_failed++;
247-
if (f) fprintf(f,"%i %i %e %e %e \n",in_block,in_prism,p.x,p.y,p.z);
247+
{
248+
// retry with boundary exclusion/inclusion reversed
249+
boolean libctl_include_boundaries=1;
250+
char *s=getenv("LIBCTL_EXCLUDE_BOUNDARIES");
251+
if (s && s[0]=='1') libctl_include_boundaries=0;
252+
in_prism=point_in_or_on_prism(the_prism.subclass.prism_data,p,1-libctl_include_boundaries);
253+
if (in_block==in_prism)
254+
num_adjusted++;
255+
}
256+
257+
if (in_block!=in_prism)
258+
{ num_failed++;
259+
if (f) fprintf(f,"%i %i %e %e %e \n",in_block,in_prism,p.x,p.y,p.z);
260+
}
248261
}
249262
if (f) fclose(f);
250263

251-
printf("point inclusion: %i/%i points failed\n",num_failed,num_tests);
264+
printf("point inclusion: %i/%i points failed (%i adjusted)\n",num_failed,num_tests,num_adjusted);
252265
return num_failed;
253266
}
254267

0 commit comments

Comments
 (0)