/* Copyright (C) 2013-2016, The Regents of The University of Michigan. All rights reserved. This software was developed in the APRIL Robotics Lab under the direction of Edwin Olson, ebolson@umich.edu. This software may be available under alternative licensing terms; contact the address above. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The views and conclusions contained in the software and documentation are those of the authors and should not be interpreted as representing official policies, either expressed or implied, of the Regents of The University of Michigan. */ #include #include #include #include #include "g2d.h" #include "common/math_util.h" #ifdef _WIN32 static inline long int random(void) { return rand(); } #endif double g2d_distance(const double a[2], const double b[2]) { return sqrtf(sq(a[0]-b[0]) + sq(a[1]-b[1])); } zarray_t *g2d_polygon_create_empty() { return zarray_create(sizeof(double[2])); } void g2d_polygon_add(zarray_t *poly, double v[2]) { zarray_add(poly, v); } zarray_t *g2d_polygon_create_data(double v[][2], int sz) { zarray_t *points = g2d_polygon_create_empty(); for (int i = 0; i < sz; i++) g2d_polygon_add(points, v[i]); return points; } zarray_t *g2d_polygon_create_zeros(int sz) { zarray_t *points = zarray_create(sizeof(double[2])); double z[2] = { 0, 0 }; for (int i = 0; i < sz; i++) zarray_add(points, z); return points; } void g2d_polygon_make_ccw(zarray_t *poly) { // Step one: we want the points in counter-clockwise order. // If the points are in clockwise order, we'll reverse them. double total_theta = 0; double last_theta = 0; // Count the angle accumulated going around the polygon. If // the sum is +2pi, it's CCW. Otherwise, we'll get -2pi. int sz = zarray_size(poly); for (int i = 0; i <= sz; i++) { double p0[2], p1[2]; zarray_get(poly, i % sz, &p0); zarray_get(poly, (i+1) % sz, &p1); double this_theta = atan2(p1[1]-p0[1], p1[0]-p0[0]); if (i > 0) { double dtheta = mod2pi(this_theta-last_theta); total_theta += dtheta; } last_theta = this_theta; } int ccw = (total_theta > 0); // reverse order if necessary. if (!ccw) { for (int i = 0; i < sz / 2; i++) { double a[2], b[2]; zarray_get(poly, i, a); zarray_get(poly, sz-1-i, b); zarray_set(poly, i, b, NULL); zarray_set(poly, sz-1-i, a, NULL); } } } int g2d_polygon_contains_point_ref(const zarray_t *poly, double q[2]) { // use winding. If the point is inside the polygon, we'll wrap // around it (accumulating 6.28 radians). If we're outside the // polygon, we'll accumulate zero. int psz = zarray_size(poly); double acc_theta = 0; double last_theta; for (int i = 0; i <= psz; i++) { double p[2]; zarray_get(poly, i % psz, &p); double this_theta = atan2(q[1]-p[1], q[0]-p[0]); if (i != 0) acc_theta += mod2pi(this_theta - last_theta); last_theta = this_theta; } return acc_theta > M_PI; } /* // sort by x coordinate, ascending static int g2d_convex_hull_sort(const void *_a, const void *_b) { double *a = (double*) _a; double *b = (double*) _b; if (a[0] < b[0]) return -1; if (a[0] == b[0]) return 0; return 1; } */ /* zarray_t *g2d_convex_hull2(const zarray_t *points) { zarray_t *hull = zarray_copy(points); zarray_sort(hull, g2d_convex_hull_sort); int hsz = zarray_size(hull); int hout = 0; for (int hin = 1; hin < hsz; hin++) { double *p; zarray_get_volatile(hull, i, &p); // Everything to the right of hin is already convex. We now // add one point, p, which begins "connected" by two // (coincident) edges from the last right-most point to p. double *last; zarray_get_volatile(hull, hout, &last); // We now remove points from the convex hull by moving } return hull; } */ // creates and returns a zarray(double[2]). The resulting polygon is // CCW and implicitly closed. Unnecessary colinear points are omitted. zarray_t *g2d_convex_hull(const zarray_t *points) { zarray_t *hull = zarray_create(sizeof(double[2])); // gift-wrap algorithm. // step 1: find left most point. int insz = zarray_size(points); // must have at least 2 points. (XXX need 3?) assert(insz >= 2); double *pleft = NULL; for (int i = 0; i < insz; i++) { double *p; zarray_get_volatile(points, i, &p); if (pleft == NULL || p[0] < pleft[0]) pleft = p; } // cannot be NULL since there must be at least one point. assert(pleft != NULL); zarray_add(hull, pleft); // step 2. gift wrap. Keep searching for points that make the // smallest-angle left-hand turn. This implementation is carefully // written to use only addition/subtraction/multiply. No division // or sqrts. This guarantees exact results for integer-coordinate // polygons (no rounding/precision problems). double *p = pleft; while (1) { assert(p != NULL); double *q = NULL; double n0 = 0, n1 = 0; // the normal to the line (p, q) (not // necessarily unit length). // Search for the point q for which the line (p,q) is most "to // the right of" the other points. (i.e., every time we find a // point that is to the right of our current line, we change // lines.) for (int i = 0; i < insz; i++) { double *thisq; zarray_get_volatile(points, i, &thisq); if (thisq == p) continue; // the first time we find another point, we initialize our // value of q, forming the line (p,q) if (q == NULL) { q = thisq; n0 = q[1] - p[1]; n1 = -q[0] + p[0]; } else { // we already have a line (p,q). is point thisq RIGHT OF line (p, q)? double e0 = thisq[0] - p[0], e1 = thisq[1] - p[1]; double dot = e0*n0 + e1*n1; if (dot > 0) { // it is. change our line. q = thisq; n0 = q[1] - p[1]; n1 = -q[0] + p[0]; } } } // we must have elected *some* line, so long as there are at // least 2 points in the polygon. assert(q != NULL); // loop completed? if (q == pleft) break; int colinear = 0; // is this new point colinear with the last two? if (zarray_size(hull) > 1) { double *o; zarray_get_volatile(hull, zarray_size(hull) - 2, &o); double e0 = o[0] - p[0]; double e1 = o[1] - p[1]; if (n0*e0 + n1*e1 == 0) colinear = 1; } // if it is colinear, overwrite the last one. if (colinear) zarray_set(hull, zarray_size(hull)-1, q, NULL); else zarray_add(hull, q); p = q; } return hull; } // Find point p on the boundary of poly that is closest to q. void g2d_polygon_closest_boundary_point(const zarray_t *poly, const double q[2], double *p) { int psz = zarray_size(poly); double min_dist = HUGE_VALF; for (int i = 0; i < psz; i++) { double *p0, *p1; zarray_get_volatile(poly, i, &p0); zarray_get_volatile(poly, (i+1) % psz, &p1); g2d_line_segment_t seg; g2d_line_segment_init_from_points(&seg, p0, p1); double thisp[2]; g2d_line_segment_closest_point(&seg, q, thisp); double dist = g2d_distance(q, thisp); if (dist < min_dist) { memcpy(p, thisp, sizeof(double[2])); min_dist = dist; } } } int g2d_polygon_contains_point(const zarray_t *poly, double q[2]) { // use winding. If the point is inside the polygon, we'll wrap // around it (accumulating 6.28 radians). If we're outside the // polygon, we'll accumulate zero. int psz = zarray_size(poly); assert(psz > 0); int last_quadrant; int quad_acc = 0; for (int i = 0; i <= psz; i++) { double *p; zarray_get_volatile(poly, i % psz, &p); // p[0] < q[0] p[1] < q[1] quadrant // 0 0 0 // 0 1 3 // 1 0 1 // 1 1 2 // p[1] < q[1] p[0] < q[0] quadrant // 0 0 0 // 0 1 1 // 1 0 3 // 1 1 2 int quadrant; if (p[0] < q[0]) quadrant = (p[1] < q[1]) ? 2 : 1; else quadrant = (p[1] < q[1]) ? 3 : 0; if (i > 0) { int dquadrant = quadrant - last_quadrant; // encourage a jump table by mapping to small positive integers. switch (dquadrant) { case -3: case 1: quad_acc ++; break; case -1: case 3: quad_acc --; break; case 0: break; case -2: case 2: { // get the previous point. double *p0; zarray_get_volatile(poly, i-1, &p0); // Consider the points p0 and p (the points around the //polygon that we are tracing) and the query point q. // // If we've moved diagonally across quadrants, we want // to measure whether we have rotated +PI radians or // -PI radians. We can test this by computing the dot // product of vector (p0-q) with the vector // perpendicular to vector (p-q) double nx = p[1] - q[1]; double ny = -p[0] + q[0]; double dot = nx*(p0[0]-q[0]) + ny*(p0[1]-q[1]); if (dot < 0) quad_acc -= 2; else quad_acc += 2; break; } } } last_quadrant = quadrant; } int v = (quad_acc >= 2) || (quad_acc <= -2); if (0 && v != g2d_polygon_contains_point_ref(poly, q)) { printf("FAILURE %d %d\n", v, quad_acc); exit(-1); } return v; } void g2d_line_init_from_points(g2d_line_t *line, const double p0[2], const double p1[2]) { line->p[0] = p0[0]; line->p[1] = p0[1]; line->u[0] = p1[0]-p0[0]; line->u[1] = p1[1]-p0[1]; double mag = sqrtf(sq(line->u[0]) + sq(line->u[1])); line->u[0] /= mag; line->u[1] /= mag; } double g2d_line_get_coordinate(const g2d_line_t *line, const double q[2]) { return (q[0]-line->p[0])*line->u[0] + (q[1]-line->p[1])*line->u[1]; } // Compute intersection of two line segments. If they intersect, // result is stored in p and 1 is returned. Otherwise, zero is // returned. p may be NULL. int g2d_line_intersect_line(const g2d_line_t *linea, const g2d_line_t *lineb, double *p) { // this implementation is many times faster than the original, // mostly due to avoiding a general-purpose LU decomposition in // Matrix.inverse(). double m00, m01, m10, m11; double i00, i01; double b00, b10; m00 = linea->u[0]; m01= -lineb->u[0]; m10 = linea->u[1]; m11= -lineb->u[1]; // determinant of m double det = m00*m11-m01*m10; // parallel lines? if (fabs(det) < 0.00000001) return 0; // inverse of m i00 = m11/det; i01 = -m01/det; b00 = lineb->p[0] - linea->p[0]; b10 = lineb->p[1] - linea->p[1]; double x00; //, x10; x00 = i00*b00+i01*b10; if (p != NULL) { p[0] = linea->u[0]*x00 + linea->p[0]; p[1] = linea->u[1]*x00 + linea->p[1]; } return 1; } void g2d_line_segment_init_from_points(g2d_line_segment_t *seg, const double p0[2], const double p1[2]) { g2d_line_init_from_points(&seg->line, p0, p1); seg->p1[0] = p1[0]; seg->p1[1] = p1[1]; } // Find the point p on segment seg that is closest to point q. void g2d_line_segment_closest_point(const g2d_line_segment_t *seg, const double *q, double *p) { double a = g2d_line_get_coordinate(&seg->line, seg->line.p); double b = g2d_line_get_coordinate(&seg->line, seg->p1); double c = g2d_line_get_coordinate(&seg->line, q); if (a < b) c = dclamp(c, a, b); else c = dclamp(c, b, a); p[0] = seg->line.p[0] + c * seg->line.u[0]; p[1] = seg->line.p[1] + c * seg->line.u[1]; } // Compute intersection of two line segments. If they intersect, // result is stored in p and 1 is returned. Otherwise, zero is // returned. p may be NULL. int g2d_line_segment_intersect_segment(const g2d_line_segment_t *sega, const g2d_line_segment_t *segb, double *p) { double tmp[2]; if (!g2d_line_intersect_line(&sega->line, &segb->line, tmp)) return 0; double a = g2d_line_get_coordinate(&sega->line, sega->line.p); double b = g2d_line_get_coordinate(&sega->line, sega->p1); double c = g2d_line_get_coordinate(&sega->line, tmp); // does intersection lie on the first line? if ((ca && c>b)) return 0; a = g2d_line_get_coordinate(&segb->line, segb->line.p); b = g2d_line_get_coordinate(&segb->line, segb->p1); c = g2d_line_get_coordinate(&segb->line, tmp); // does intersection lie on second line? if ((ca && c>b)) return 0; if (p != NULL) { p[0] = tmp[0]; p[1] = tmp[1]; } return 1; } // Compute intersection of a line segment and a line. If they // intersect, result is stored in p and 1 is returned. Otherwise, zero // is returned. p may be NULL. int g2d_line_segment_intersect_line(const g2d_line_segment_t *seg, const g2d_line_t *line, double *p) { double tmp[2]; if (!g2d_line_intersect_line(&seg->line, line, tmp)) return 0; double a = g2d_line_get_coordinate(&seg->line, seg->line.p); double b = g2d_line_get_coordinate(&seg->line, seg->p1); double c = g2d_line_get_coordinate(&seg->line, tmp); // does intersection lie on the first line? if ((ca && c>b)) return 0; if (p != NULL) { p[0] = tmp[0]; p[1] = tmp[1]; } return 1; } // do the edges of polya and polyb collide? (Does NOT test for containment). int g2d_polygon_intersects_polygon(const zarray_t *polya, const zarray_t *polyb) { // do any of the line segments collide? If so, the answer is no. // dumb N^2 method. for (int ia = 0; ia < zarray_size(polya); ia++) { double pa0[2], pa1[2]; zarray_get(polya, ia, pa0); zarray_get(polya, (ia+1)%zarray_size(polya), pa1); g2d_line_segment_t sega; g2d_line_segment_init_from_points(&sega, pa0, pa1); for (int ib = 0; ib < zarray_size(polyb); ib++) { double pb0[2], pb1[2]; zarray_get(polyb, ib, pb0); zarray_get(polyb, (ib+1)%zarray_size(polyb), pb1); g2d_line_segment_t segb; g2d_line_segment_init_from_points(&segb, pb0, pb1); if (g2d_line_segment_intersect_segment(&sega, &segb, NULL)) return 1; } } return 0; } // does polya completely contain polyb? int g2d_polygon_contains_polygon(const zarray_t *polya, const zarray_t *polyb) { // do any of the line segments collide? If so, the answer is no. if (g2d_polygon_intersects_polygon(polya, polyb)) return 0; // if none of the edges cross, then the polygon is either fully // contained or fully outside. double p[2]; zarray_get(polyb, 0, p); return g2d_polygon_contains_point(polya, p); } // compute a point that is inside the polygon. (It may not be *far* inside though) void g2d_polygon_get_interior_point(const zarray_t *poly, double *p) { // take the first three points, which form a triangle. Find the middle point double a[2], b[2], c[2]; zarray_get(poly, 0, a); zarray_get(poly, 1, b); zarray_get(poly, 2, c); p[0] = (a[0]+b[0]+c[0])/3; p[1] = (a[1]+b[1]+c[1])/3; } int g2d_polygon_overlaps_polygon(const zarray_t *polya, const zarray_t *polyb) { // do any of the line segments collide? If so, the answer is yes. if (g2d_polygon_intersects_polygon(polya, polyb)) return 1; // if none of the edges cross, then the polygon is either fully // contained or fully outside. double p[2]; g2d_polygon_get_interior_point(polyb, p); if (g2d_polygon_contains_point(polya, p)) return 1; g2d_polygon_get_interior_point(polya, p); if (g2d_polygon_contains_point(polyb, p)) return 1; return 0; } static int double_sort_up(const void *_a, const void *_b) { double a = *((double*) _a); double b = *((double*) _b); if (a < b) return -1; if (a == b) return 0; return 1; } // Compute the crossings of the polygon along line y, storing them in // the array x. X must be allocated to be at least as long as // zarray_size(poly). X will be sorted, ready for // rasterization. Returns the number of intersections (and elements // written to x). /* To rasterize, do something like this: double res = 0.099; for (double y = y0; y < y1; y += res) { double xs[zarray_size(poly)]; int xsz = g2d_polygon_rasterize(poly, y, xs); int xpos = 0; int inout = 0; // start off "out" for (double x = x0; x < x1; x += res) { while (x > xs[xpos] && xpos < xsz) { xpos++; inout ^= 1; } if (inout) printf("y"); else printf(" "); } printf("\n"); */ // returns the number of x intercepts int g2d_polygon_rasterize(const zarray_t *poly, double y, double *x) { int sz = zarray_size(poly); g2d_line_t line; if (1) { double p0[2] = { 0, y }; double p1[2] = { 1, y }; g2d_line_init_from_points(&line, p0, p1); } int xpos = 0; for (int i = 0; i < sz; i++) { g2d_line_segment_t seg; double *p0, *p1; zarray_get_volatile(poly, i, &p0); zarray_get_volatile(poly, (i+1)%sz, &p1); g2d_line_segment_init_from_points(&seg, p0, p1); double q[2]; if (g2d_line_segment_intersect_line(&seg, &line, q)) x[xpos++] = q[0]; } qsort(x, xpos, sizeof(double), double_sort_up); return xpos; } /* /---(1,5) (-2,4)-/ | \ | \ (1,2)--(2,2)\ \ \ \ \ (0,0)------------------(4,0) */ #if 0 #include "timeprofile.h" int main(int argc, char *argv[]) { timeprofile_t *tp = timeprofile_create(); zarray_t *polya = g2d_polygon_create_data((double[][2]) { { 0, 0}, { 4, 0}, { 2, 2}, { 1, 2}, { 1, 5}, { -2,4} }, 6); zarray_t *polyb = g2d_polygon_create_data((double[][2]) { { .1, .1}, { .5, .1}, { .1, .5 } }, 3); zarray_t *polyc = g2d_polygon_create_data((double[][2]) { { 3, 0}, { 5, 0}, { 5, 1} }, 3); zarray_t *polyd = g2d_polygon_create_data((double[][2]) { { 5, 5}, { 6, 6}, { 5, 6} }, 3); /* 5 L---K 4 |I--J 3 |H-G 2 |E-F 1 |D--C 0 A---B 01234 */ zarray_t *polyE = g2d_polygon_create_data((double[][2]) { {0,0}, {4,0}, {4, 1}, {1,1}, {1,2}, {3,2}, {3,3}, {1,3}, {1,4}, {4,4}, {4,5}, {0,5}}, 12); srand(0); timeprofile_stamp(tp, "begin"); if (1) { int niters = 100000; for (int i = 0; i < niters; i++) { double q[2]; q[0] = 10.0f * random() / RAND_MAX - 2; q[1] = 10.0f * random() / RAND_MAX - 2; g2d_polygon_contains_point(polyE, q); } timeprofile_stamp(tp, "fast"); for (int i = 0; i < niters; i++) { double q[2]; q[0] = 10.0f * random() / RAND_MAX - 2; q[1] = 10.0f * random() / RAND_MAX - 2; g2d_polygon_contains_point_ref(polyE, q); } timeprofile_stamp(tp, "slow"); for (int i = 0; i < niters; i++) { double q[2]; q[0] = 10.0f * random() / RAND_MAX - 2; q[1] = 10.0f * random() / RAND_MAX - 2; int v0 = g2d_polygon_contains_point(polyE, q); int v1 = g2d_polygon_contains_point_ref(polyE, q); assert(v0 == v1); } timeprofile_stamp(tp, "both"); timeprofile_display(tp); } if (1) { zarray_t *poly = polyE; double res = 0.399; for (double y = 5.2; y >= -.5; y -= res) { double xs[zarray_size(poly)]; int xsz = g2d_polygon_rasterize(poly, y, xs); int xpos = 0; int inout = 0; // start off "out" for (double x = -3; x < 6; x += res) { while (x > xs[xpos] && xpos < xsz) { xpos++; inout ^= 1; } if (inout) printf("y"); else printf(" "); } printf("\n"); for (double x = -3; x < 6; x += res) { double q[2] = {x, y}; if (g2d_polygon_contains_point(poly, q)) printf("X"); else printf(" "); } printf("\n"); } } /* // CW order double p[][2] = { { 0, 0}, { -2, 4}, {1, 5}, {1, 2}, {2, 2}, {4, 0} }; */ double q[2] = { 10, 10 }; printf("0==%d\n", g2d_polygon_contains_point(polya, q)); q[0] = 1; q[1] = 1; printf("1==%d\n", g2d_polygon_contains_point(polya, q)); q[0] = 3; q[1] = .5; printf("1==%d\n", g2d_polygon_contains_point(polya, q)); q[0] = 1.2; q[1] = 2.1; printf("0==%d\n", g2d_polygon_contains_point(polya, q)); printf("0==%d\n", g2d_polygon_contains_polygon(polya, polyb)); printf("0==%d\n", g2d_polygon_contains_polygon(polya, polyc)); printf("0==%d\n", g2d_polygon_contains_polygon(polya, polyd)); //////////////////////////////////////////////////////// // Test convex hull if (1) { zarray_t *hull = g2d_convex_hull(polyE); for (int k = 0; k < zarray_size(hull); k++) { double *h; zarray_get_volatile(hull, k, &h); printf("%15f, %15f\n", h[0], h[1]); } } for (int i = 0; i < 100000; i++) { zarray_t *points = zarray_create(sizeof(double[2])); for (int j = 0; j < 100; j++) { double q[2]; q[0] = 10.0f * random() / RAND_MAX - 2; q[1] = 10.0f * random() / RAND_MAX - 2; zarray_add(points, q); } zarray_t *hull = g2d_convex_hull(points); for (int j = 0; j < zarray_size(points); j++) { double *q; zarray_get_volatile(points, j, &q); int on_edge; double p[2]; g2d_polygon_closest_boundary_point(hull, q, p); if (g2d_distance(q, p) < .00001) on_edge = 1; assert(on_edge || g2d_polygon_contains_point(hull, q)); } zarray_destroy(hull); zarray_destroy(points); } } #endif