// file php_polygon.cpp // This is a port of a php class written by Brenor Brophy (see below) /*------------------------------------------------------------------------------ ** File: polygon.php ** Description: PHP class for a polygon. ** Version: 1.1 ** Author: Brenor Brophy ** Email: brenor at sbcglobal dot net ** Homepage: www.brenorbrophy.com **------------------------------------------------------------------------------ ** COPYRIGHT (c) 2005 BRENOR BROPHY ** ** The source code included in this package is free software; you can ** redistribute it and/or modify it under the terms of the GNU General Public ** License as published by the Free Software Foundation. This license can be ** read at: ** ** http://www.opensource.org/licenses/gpl-license.php ** ** This program is distributed in the hope that it will be useful, but WITHOUT ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS ** FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. **------------------------------------------------------------------------------ ** ** Based on the paper "Efficient Clipping of Arbitary Polygons" by Gunther ** Greiner (greiner at informatik dot uni-erlangen dot de) and Kai Hormann ** (hormann at informatik dot tu-clausthal dot de), ACM Transactions on Graphics ** 1998;17(2):71-83. ** ** Available at: www.in.tu-clausthal.de/~hormann/papers/clipping.pdf ** ** Another useful site describing the algorithm and with some example ** C code by Ionel Daniel Stroe is at: ** ** http://davis.wpi.edu/~matt/courses/clipping/ ** ** The algorithm is extended by Brenor Brophy to allow polygons with ** arcs between vertices. ** ** Rev History ** ----------------------------------------------------------------------------- ** 1.0 08/25/2005 Initial Release ** 1.1 09/04/2005 Added Move(), Rotate(), isPolyInside() and bRect() methods. ** Added software license language to header comments */ //#include "stdafx.h" #include #include #include "fctsys.h" #include "php_polygon_vertex.h" #include "php_polygon.h" const double PT = 0.99999; //const double eps = (1.0 - PT)/10.0; const double eps = 0.0; polygon::polygon( vertex* first ) { m_first = first; m_cnt = 0; } polygon::~polygon() { while( m_cnt > 1 ) { vertex* v = getFirst(); del( v->m_nextV ); } if( m_first ) { delete m_first; } } vertex* polygon::getFirst() { return m_first; } polygon* polygon::NextPoly() { return m_first->NextPoly(); } /* ** Add a vertex object to the polygon (vertex is added at the "end" of the list) ** Which because polygons are closed lists means it is added just before the first ** vertex. */ void polygon::add( vertex* nv ) { if( m_cnt == 0 ) // If this is the first vertex in the polygon { m_first = nv; // Save a reference to it in the polygon m_first->setNext( nv ); // Set its pointer to point to itself m_first->setPrev( nv ); // because it is the only vertex in the list segment* ps = m_first->Nseg(); // Get ref to the Next segment object m_first->setPseg( ps ); // and save it as Prev segment as well } else // At least one other vertex already exists { // p <-> nv <-> n // ps ns vertex* n = m_first; // Get a ref to the first vertex in the list vertex* p = n->Prev(); // Get ref to previous vertex n->setPrev( nv ); // Add at end of list (just before first) nv->setNext( n ); // link the new vertex to it nv->setPrev( p ); // link to the pervious EOL vertex p->setNext( nv ); // And finally link the previous EOL vertex // Segments segment* ns = nv->Nseg(); // Get ref to the new next segment segment* ps = p->Nseg(); // Get ref to the previous segment n->setPseg( ns ); // Set new previous seg for m_first nv->setPseg( ps ); // Set previous seg of the new vertex } m_cnt++; // Increment the count of vertices } /* ** Create a vertex and then add it to the polygon */ void polygon::addv( double x, double y, double xc, double yc, int d ) { vertex* nv = new vertex( x, y, xc, yc, d ); add( nv ); } /* ** Delete a vertex object from the polygon. This is not used by the main algorithm ** but instead is used to clean-up a polygon so that a second boolean operation can ** be performed. */ vertex* polygon::del( vertex* v ) { // p <-> v <-> n Will delete v and ns // ps ns vertex* p = v->Prev(); // Get ref to previous vertex vertex* n = v->Next(); // Get ref to next vertex p->setNext( n ); // Link previous forward to next n->setPrev( p ); // Link next back to previous // Segments segment* ps = p->Nseg(); // Get ref to previous segment segment* ns = v->Nseg(); // Get ref to next segment n->setPseg( ps ); // Link next back to previous segment delete ns; //AMW v->m_nSeg = NULL; // AMW delete v; //AMW // ns = NULL; // v = NULL; // Free the memory m_cnt--; // One less vertex return n; // Return a ref to the next valid vertex } /* ** Reset Polygon - Deletes all intersection vertices. This is used to ** restore a polygon that has been processed by the boolean method ** so that it can be processed again. */ void polygon::res() { vertex* v = getFirst(); // Get the first vertex do { v = v->Next(); // Get the next vertex in the polygon while( v->isIntersect() ) // Delete all intersection vertices v = del( v ); } while( v->id() != m_first->id() ); } /* ** Copy Polygon - Returns a reference to a new copy of the poly object ** including all its vertices & their segments */ polygon* polygon::copy_poly() { polygon* n = new polygon; // Create a new instance of this class vertex* v = getFirst(); do { n->addv( v->X(), v->Y(), v->Xc(), v->Yc(), (int) v->d() ); v = v->Next(); } while( v->id() != m_first->id() ); return n; } /* ** Insert and Sort a vertex between a specified pair of vertices (start and end) ** ** This function inserts a vertex (most likely an intersection point) between two ** other vertices. These other vertices cannot be intersections (that is they must ** be actual vertices of the original polygon). If there are multiple intersection ** points between the two vertices then the new vertex is inserted based on its ** alpha value. */ void polygon::insertSort( vertex* nv, vertex* s, vertex* e ) { vertex* c = s; // Set current to the starting vertex // Move current past any intersections // whose alpha is lower but don't go past // the end vertex while( c->id() != e->id() && c->Alpha() < nv->Alpha() ) c = c->Next(); // p <-> nv <-> c nv->setNext( c ); // Link new vertex forward to curent one vertex* p = c->Prev(); // Get a link to the previous vertex nv->setPrev( p ); // Link the new vertex back to the previous one p->setNext( nv ); // Link previous vertex forward to new vertex c->setPrev( nv ); // Link current vertex back to the new vertex // Segments segment* ps = p->Nseg(); nv->setPseg( ps ); segment* ns = nv->Nseg(); c->setPseg( ns ); m_cnt++; // Just added a new vertex } /* ** return the next non intersecting vertex after the one specified */ vertex* polygon::nxt( vertex* v ) { vertex* c = v; // Initialize current vertex while( c && c->isIntersect() ) // Move until a non-intersection c = c->Next(); // vertex if found return c; // return that vertex } /* ** Check if any unchecked intersections remain in the polygon. The boolean ** method is complete when all intersections have been checked. */ BOOL polygon::unckd_remain() { BOOL remain = FALSE; vertex* v = m_first; do { if( v->isIntersect() && !v->isChecked() ) remain = TRUE; // Set if an unchecked intersection is found v = v->Next(); } while( v->id() != m_first->id() ); return remain; } /* ** Return a ref to the first unchecked intersection point in the polygon. ** If none are found then just the first vertex is returned. */ vertex* polygon::first_unckd_intersect() { vertex* v = m_first; do // Do-While { // Not yet reached end of the polygon v = v->Next(); // AND the vertex if NOT an intersection } // OR it IS an intersection, but has been checked already while( v->id() != m_first->id() && ( !v->isIntersect() || ( v->isIntersect() && v->isChecked() ) ) ); return v; } /* ** Return the distance between two points */ double polygon::dist( double x1, double y1, double x2, double y2 ) { return sqrt( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) ); } /* ** Calculate the angle between 2 points, where Xc,Yc is the center of a circle ** and x,y is a point on its circumference. All angles are relative to ** the 3 O'Clock position. Result returned in radians */ double polygon::angle( double xc, double yc, double x1, double y1 ) { double d = dist( xc, yc, x1, y1 ); // calc distance between two points double a1; if( asin( (y1 - yc) / d ) >= 0 ) a1 = acos( (x1 - xc) / d ); else a1 = 2 * PI - acos( (x1 - xc) / d ); return a1; } /* ** Return Alpha value for an Arc ** ** X1/Y1 & X2/Y2 are the end points of the arc, Xc/Yc is the center & Xi/Yi ** the intersection point on the arc. d is the direction of the arc */ double polygon::aAlpha( double x1, double y1, double x2, double y2, double xc, double yc, double xi, double yi, double d ) { double sa = angle( xc, yc, x1, y1 ); // Start Angle double ea = angle( xc, yc, x2, y2 ); // End Angle double ia = angle( xc, yc, xi, yi ); // Intersection Angle double arc, aint; if( d == 1 ) // Anti-Clockwise { arc = ea - sa; aint = ia - sa; } else // Clockwise { arc = sa - ea; aint = sa - ia; } if( arc < 0 ) arc += 2 * PI; if( aint < 0 ) aint += 2 * PI; double a = aint / arc; return a; } /* ** This function handles the degenerate case where a vertex of one ** polygon lies directly on an edge of the other. This case can ** also occur during the isInside() function, where the search ** line exactly intersects with a vertex. The function works ** by shortening the line by a tiny amount. */ void polygon::perturb( vertex* p1, vertex* p2, vertex* q1, vertex* q2, double aP, double aQ ) { // if (aP == 0) // Move vertex p1 closer to p2 if( abs( aP ) <= eps ) // Move vertex p1 closer to p2 { p1->setX( p1->X() + (1 - PT) * ( p2->X() - p1->X() ) ); p1->setY( p1->Y() + (1 - PT) * ( p2->Y() - p1->Y() ) ); } // else if (aP == 1) // Move vertex p2 closer to p1 else if( abs( 1 - aP ) <= eps ) // Move vertex p2 closer to p1 { p2->setX( p1->X() + PT * ( p2->X() - p1->X() ) ); p2->setY( p1->Y() + PT * ( p2->Y() - p1->Y() ) ); } //** else if (aQ == 0) // Move vertex q1 closer to q2 if( abs( aQ ) <= eps ) // Move vertex q1 closer to q2 { q1->setX( q1->X() + (1 - PT) * ( q2->X() - q1->X() ) ); q1->setY( q1->Y() + (1 - PT) * ( q2->Y() - q1->Y() ) ); } //** else if (aQ == 1) // Move vertex q2 closer to q1 else if( abs( 1 - aQ ) <= eps ) // Move vertex q2 closer to q1 { q2->setX( q1->X() + PT * ( q2->X() - q1->X() ) ); q2->setY( q1->Y() + PT * ( q2->Y() - q1->Y() ) ); } } /* ** Determine the intersection between two pairs of vertices p1/p2, q1/q2 ** ** Either or both of the segments passed to this function could be arcs. ** Thus we must first determine if the intersection is line/line, arc/line ** or arc/arc. Then apply the correct math to calculate the intersection(s). ** ** Line/Line can have 0 (no intersection) or 1 intersection ** Line/Arc and Arc/Arc can have 0, 1 or 2 intersections ** ** The function returns TRUE is any intersections are found ** The number found is returned in n ** The arrays ix[], iy[], alphaP[] & alphaQ[] return the intersection points ** and their associated alpha values. */ BOOL polygon::ints( vertex* p1, vertex* p2, vertex* q1, vertex* q2, int* n, double ix[], double iy[], double alphaP[], double alphaQ[] ) { BOOL found = FALSE; *n = 0; // No intersections found yet int pt = (int) p1->d(); int qt = (int) q1->d(); // Do we have Arcs or Lines? if( pt == 0 && qt == 0 ) // Is it line/Line ? { /* LINE/LINE ** Algorithm from: http://astronomy.swin.edu.au/~pbourke/geometry/lineline2d/ */ double x1 = p1->X(); double y1 = p1->Y(); double x2 = p2->X(); double y2 = p2->Y(); double x3 = q1->X(); double y3 = q1->Y(); double x4 = q2->X(); double y4 = q2->Y(); double d = ( (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1) ); if( d != 0 ) { // The lines intersect at a point somewhere double ua = ( (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3) ) / d; double ub = ( (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3) ) / d; // TRACE( " ints: ua = %.17f, ub = %.17f\n", ua, ub ); // The values of $ua and $ub tell us where the intersection occurred. // A value between 0 and 1 means the intersection occurred within the // line segment. // A value less than 0 or greater than 1 means the intersection occurred // outside the line segment // A value of exactly 0 or 1 means the intersection occurred right at the // start or end of the line segment. For our purposes we will consider this // NOT to be an intersection and we will move the vertex a tiny distance // away from the intersecting line. // if( ua == 0 || ua == 1 || ub == 0 || ub == 1 ) if( abs( ua )<=eps || abs( 1.0 - ua )<=eps || abs( ub )<=eps || abs( 1.0 - ub )<=eps ) { // Degenerate case - vertex touches a line perturb( p1, p2, q1, q2, ua, ub ); //** for testing, see if we have successfully resolved the degeneracy { double tx1 = p1->X(); double ty1 = p1->Y(); double tx2 = p2->X(); double ty2 = p2->Y(); double tx3 = q1->X(); double ty3 = q1->Y(); double tx4 = q2->X(); double ty4 = q2->Y(); double td = ( (ty4 - ty3) * (tx2 - tx1) - (tx4 - tx3) * (ty2 - ty1) ); if( td != 0 ) { // The lines intersect at a point somewhere double tua = ( (tx4 - tx3) * (ty1 - ty3) - (ty4 - ty3) * (tx1 - tx3) ) / td; double tub = ( (tx2 - tx1) * (ty1 - ty3) - (ty2 - ty1) * (tx1 - tx3) ) / td; if( abs( tua )<=eps || abs( 1.0 - tua )<=eps || abs( tub )<=eps || abs( 1.0 - tub )<=eps ) wxASSERT( 0 ); else if( (tua > 0 && tua < 1) && (tub > 0 && tub < 1) ) wxASSERT( 0 ); TRACE( " perturb:\n new s = (%f,%f) to (%f,%f)\n new c = (%f,%f) to (%f,%f)\n new ua = %.17f, ub = %.17f\n", tx1, ty1, tx2, ty2, tx3, ty3, tx4, ty4, tua, tub ); } } //** end test found = FALSE; } else if( (ua > 0 && ua < 1) && (ub > 0 && ub < 1) ) { // Intersection occurs on both line segments double x = x1 + ua * (x2 - x1); double y = y1 + ua * (y2 - y1); iy[0] = y; ix[0] = x; alphaP[0] = ua; alphaQ[0] = ub; *n = 1; found = TRUE; } else { // The lines do not intersect found = FALSE; } } else { // The lines do not intersect (they are parallel) found = FALSE; } } // End of find Line/Line intersection else if( pt != 0 && qt != 0 ) // Is it Arc/Arc? { /* ARC/ARC ** Algorithm from: http://astronomy.swin.edu.au/~pbourke/geometry/2circle/ */ double x0 = p1->Xc(); double y0 = p1->Yc(); // Center of first Arc double r0 = dist( x0, y0, p1->X(), p1->Y() ); // Calc the radius double x1 = q1->Xc(); double y1 = q1->Yc(); // Center of second Arc double r1 = dist( x1, y1, q1->X(), q1->Y() ); // Calc the radius double dx = x1 - x0; // dx and dy are the vertical and horizontal double dy = y1 - y0; // distances between the circle centers. double d = sqrt( (dy * dy) + (dx * dx) ); // Distance between the centers. if( d > (r0 + r1) ) // Check for solvability. { // no solution. circles do not intersect. found = FALSE; } else if( d < abs( r0 - r1 ) ) { // no solution. one circle inside the other found = FALSE; } else { /* ** 'xy2' is the point where the line through the circle intersection ** points crosses the line between the circle centers. */ double a = ( (r0 * r0) - (r1 * r1) + (d * d) ) / (2.0 * d); // Calc the distance from xy0 to xy2. double x2 = x0 + (dx * a / d); // Determine the coordinates of xy2. double y2 = y0 + (dy * a / d); if( d == (r0 + r1) ) // Arcs touch at xy2 exactly (unlikely) { alphaP[0] = aAlpha( p1->X(), p1->Y(), p2->X(), p2->Y(), x0, y0, x2, y2, pt ); alphaQ[0] = aAlpha( q1->X(), q1->Y(), q2->X(), q2->Y(), x1, y1, x2, y2, qt ); if( (alphaP[0] >0 && alphaP[0] < 1) && (alphaQ[0] >0 && alphaQ[0] < 1) ) { ix[0] = x2; iy[0] = y2; *n = 1; found = TRUE; } } else // Arcs intersect at two points { double alP[2], alQ[2]; double h = sqrt( (r0 * r0) - (a * a) ); // Calc the distance from xy2 to either // of the intersection points. double rx = -dy * (h / d); // Now determine the offsets of the double ry = dx * (h / d); // intersection points from xy2 double x[2], y[2]; x[0] = x2 + rx; x[1] = x2 - rx; // Calc the absolute intersection points. y[0] = y2 + ry; y[1] = y2 - ry; alP[0] = aAlpha( p1->X(), p1->Y(), p2->X(), p2->Y(), x0, y0, x[0], y[0], pt ); alQ[0] = aAlpha( q1->X(), q1->Y(), q2->X(), q2->Y(), x1, y1, x[0], y[0], qt ); alP[1] = aAlpha( p1->X(), p1->Y(), p2->X(), p2->Y(), x0, y0, x[1], y[1], pt ); alQ[1] = aAlpha( q1->X(), q1->Y(), q2->X(), q2->Y(), x1, y1, x[1], y[1], qt ); for( int i = 0; i<=1; i++ ) if( (alP[i] >0 && alP[i] < 1) && (alQ[i] >0 && alQ[i] < 1) ) { ix[*n] = x[i]; iy[*n] = y[i]; alphaP[*n] = alP[i]; alphaQ[*n] = alQ[i]; *n++; found = TRUE; } } } } // End of find Arc/Arc intersection else // It must be Arc/Line { /* ARC/LINE ** Algorithm from: http://astronomy.swin.edu.au/~pbourke/geometry/sphereline/ */ double d, x1, x2, xc, xs, xe; double y1, y2, yc, ys, ye; if( pt == 0 ) // Segment p1,p2 is the line { // Segment q1,q2 is the arc x1 = p1->X(); y1 = p1->Y(); x2 = p2->X(); y2 = p2->Y(); xc = q1->Xc(); yc = q1->Yc(); xs = q1->X(); ys = q1->Y(); xe = q2->X(); ye = q2->Y(); d = qt; } else // Segment q1,q2 is the line { // Segment p1,p2 is the arc x1 = q1->X(); y1 = q1->Y(); x2 = q2->X(); y2 = q2->Y(); xc = p1->Xc(); yc = p1->Yc(); xs = p1->X(); ys = p1->Y(); xe = p2->X(); ye = p2->Y(); d = pt; } double r = dist( xc, yc, xs, ys ); double a = pow( (x2 - x1), 2 ) + pow( (y2 - y1), 2 ); double b = 2 * ( (x2 - x1) * (x1 - xc) + (y2 - y1) * (y1 - yc) ); double c = pow( xc, 2 ) + pow( yc, 2 ) + pow( x1, 2 ) + pow( y1, 2 ) - 2 * ( xc * x1 + yc * y1) - pow( r, 2 ); double i = b * b - 4 * a * c; if( i < 0.0 ) // no intersection { found = FALSE; } else if( i == 0.0 ) // one intersection { double mu = -b / (2 * a); double x = x1 + mu * (x2 - x1); double y = y1 + mu * (y2 - y1); double al = mu; // Line Alpha double aa = this->aAlpha( xs, ys, xe, ye, xc, yc, x, y, d ); // Arc Alpha if( (al >0 && al <1)&&(aa >0 && aa <1) ) { ix[0] = x; iy[0] = y; *n = 1; found = TRUE; if( pt == 0 ) { alphaP[0] = al; alphaQ[0] = aa; } else { alphaP[0] = aa; alphaQ[0] = al; } } } else if( i > 0.0 ) // two intersections { double mu[2], x[2], y[2], al[2], aa[2]; mu[0] = ( -b + sqrt( pow( b, 2 ) - 4 * a * c ) ) / (2 * a); // first intersection x[0] = x1 + mu[0] * (x2 - x1); y[0] = y1 + mu[0] * (y2 - y1); mu[1] = ( -b - sqrt( pow( b, 2 ) - 4 * a * c ) ) / (2 * a); // second intersection x[1] = x1 + mu[1] * (x2 - x1); y[1] = y1 + mu[1] * (y2 - y1); al[0] = mu[0]; aa[0] = aAlpha( xs, ys, xe, ye, xc, yc, x[0], y[0], d ); al[1] = mu[1]; aa[1] = aAlpha( xs, ys, xe, ye, xc, yc, x[1], y[1], d ); for( int i = 0; i<=1; i++ ) if( (al[i] >0 && al[i] < 1) && (aa[i] >0 && aa[i] < 1) ) { ix[*n] = x[i]; iy[*n] = y[i]; if( pt == 0 ) { alphaP[*n] = al[i]; alphaQ[*n] = aa[i]; } else { alphaP[*n] = aa[i]; alphaQ[*n] = al[i]; } *n++; found = TRUE; } } } // End of find Arc/Line intersection return found; } // end of intersect function /* ** Test if a vertex lies inside the polygon ** ** This function calculates the "winding" number for the point. This number ** represents the number of times a ray emitted from the point to infinity ** intersects any edge of the polygon. An even winding number means the point ** lies OUTSIDE the polygon, an odd number means it lies INSIDE it. ** ** Right now infinity is set to -10000000, some people might argue that infinity ** actually is a bit bigger. Those people have no lives. ** ** Allan Wright 4/16/2006: I guess I have no life: I had to increase it to -1000000000 */ BOOL polygon::isInside( vertex* v ) { //** modified for testing if( v->isIntersect() ) wxASSERT( 0 ); int winding_number = 0; int winding_number2 = 0; int winding_number3 = 0; int winding_number4 = 0; //** vertex * point_at_infinity = new vertex(-10000000,v->Y()); // Create point at infinity /* vertex * point_at_infinity = new vertex(-1000000000,-50000000); // Create point at infinity * vertex * point_at_infinity2 = new vertex(1000000000,+50000000); // Create point at infinity * vertex * point_at_infinity3 = new vertex(500000000,1000000000); // Create point at infinity * vertex * point_at_infinity4 = new vertex(-500000000,1000000000); // Create point at infinity */ vertex point_at_infinity( -1000000000, -50000000 ); // Create point at infinity vertex point_at_infinity2( 1000000000, +50000000 ); // Create point at infinity vertex point_at_infinity3( 500000000, 1000000000 ); // Create point at infinity vertex point_at_infinity4( -500000000, 1000000000 ); // Create point at infinity vertex* q = m_first; // End vertex of a line segment in polygon do { if( !q->isIntersect() ) { int n; double x[2], y[2], aP[2], aQ[2]; if( ints( &point_at_infinity, v, q, nxt( q->Next() ), &n, x, y, aP, aQ ) ) winding_number += n; // Add number of intersections found if( ints( &point_at_infinity2, v, q, nxt( q->Next() ), &n, x, y, aP, aQ ) ) winding_number2 += n; // Add number of intersections found if( ints( &point_at_infinity3, v, q, nxt( q->Next() ), &n, x, y, aP, aQ ) ) winding_number3 += n; // Add number of intersections found if( ints( &point_at_infinity4, v, q, nxt( q->Next() ), &n, x, y, aP, aQ ) ) winding_number4 += n; // Add number of intersections found } q = q->Next(); } while( q->id() != m_first->id() ); // delete point_at_infinity; // delete point_at_infinity2; if( winding_number % 2 != winding_number2 % 2 || winding_number3 % 2 != winding_number4 % 2 || winding_number % 2 != winding_number3 % 2 ) wxASSERT( 0 ); if( winding_number % 2 == 0 ) // Check even or odd return FALSE; // even == outside else return TRUE; // odd == inside } /* ** Execute a Boolean operation on a polygon ** ** This is the key method. It allows you to AND/OR this polygon with another one ** (equvalent to a UNION or INTERSECT operation. You may also subtract one from ** the other (same as DIFFERENCE). Given two polygons A, B the following operations ** may be performed: ** ** A|B ... A OR B (Union of A and B) ** A&B ... A AND B (Intersection of A and B) ** A\B ... A - B ** B\A ... B - A ** ** A is the object and B is the polygon passed to the method. */ polygon* polygon::boolean( polygon* polyB, int oper ) { polygon* last = NULL; vertex* s = m_first; // First vertex of the subject polygon vertex* c = polyB->getFirst(); // First vertex of the "clip" polygon /* ** Phase 1 of the algoritm is to find all intersection points between the two ** polygons. A new vertex is created for each intersection and it is added to ** the linked lists for both polygons. The "neighbor" reference in each vertex ** stores the link between the same intersection point in each polygon. */ TRACE( "boolean...phase 1\n" ); do { TRACE( "s=(%f,%f) to (%f,%f) I=%d\n", s->m_x, s->m_y, s->m_nextV->m_x, s->m_nextV->m_y, s->m_intersect ); if( !s->isIntersect() ) { do { TRACE( " c=(%f,%f) to (%f,%f) I=%d\n", c->m_x, c->m_y, c->m_nextV->m_x, c->m_nextV->m_y, c->m_intersect ); if( !c->isIntersect() ) { int n; double ix[2], iy[2], alphaS[2], alphaC[2]; BOOL bInt = ints( s, nxt( s->Next() ), c, polyB->nxt( c->Next() ), &n, ix, iy, alphaS, alphaC ); if( bInt ) { TRACE( " int at (%f,%f) aS = %.17f, aC = %.17f\n", ix[0], iy[0], alphaS[0], alphaC[0] ); for( int i = 0; iXc(), s->Yc(), s->d(), NULL, NULL, NULL, TRUE, NULL, alphaS[i], FALSE, FALSE ); vertex* ic = new vertex( ix[i], iy[i], c->Xc(), c->Yc(), c->d(), NULL, NULL, NULL, TRUE, NULL, alphaC[i], FALSE, FALSE ); is->setNeighbor( ic ); ic->setNeighbor( is ); insertSort( is, s, this->nxt( s->Next() ) ); polyB->insertSort( ic, c, polyB->nxt( c->Next() ) ); } } } // end if c is not an intersect point c = c->Next(); } while( c->id() != polyB->m_first->id() ); } // end if s not an intersect point s = s->Next(); } while( s->id() != m_first->id() ); //** for testing...check number of intersections in each poly TRACE( "boolean...phase 1 testing\n" ); int n_ints = 0; s = m_first; do { if( s->isIntersect() ) n_ints++; s = s->Next(); } while( s->id() != m_first->id() ); int n_polyB_ints = 0; s = polyB->m_first; do { if( s->isIntersect() ) n_polyB_ints++; s = s->Next(); } while( s->id() != polyB->m_first->id() ); if( n_ints != n_polyB_ints ) wxASSERT( 0 ); if( n_ints % 2 != 0 ) wxASSERT( 0 ); //** end test /* ** Phase 2 of the algorithm is to identify every intersection point as an ** entry or exit point to the other polygon. This will set the entry bits ** in each vertex object. ** ** What is really stored in the entry record for each intersection is the ** direction the algorithm should take when it arrives at that entry point. ** Depending in the operation requested (A&B, A|B, A/B, B/A) the direction is ** set as follows for entry points (f=foreward, b=Back), exit points are always set ** to the opposite: ** Enter Exit ** A B A B ** A|B b b f f ** A&B f f b b ** A\B b f f b ** B\A f b b f ** ** f = TRUE, b = FALSE when stored in the entry record */ TRACE( "boolean...phase 2\n" ); BOOL A, B; switch( oper ) { case A_OR_B: A = FALSE; B = FALSE; break; case A_AND_B: A = TRUE; B = TRUE; break; case A_MINUS_B: A = FALSE; B = TRUE; break; case B_MINUS_A: A = TRUE; B = FALSE; break; default: A = TRUE; B = TRUE; break; } s = m_first; //** testing if( s->isIntersect() ) wxASSERT( 0 ); //** end test BOOL entry; if( polyB->isInside( s ) ) // if we are already inside entry = !A; // next intersection must be an exit else // otherwise entry = A; // next intersection must be an entry do { if( s->isIntersect() ) { s->setEntry( entry ); entry = !entry; } s = s->Next(); } while( s->id() != m_first->id() ); /* ** Repeat for other polygon */ c = polyB->m_first; if( this->isInside( c ) ) // if we are already inside entry = !B; // next intersection must be an exit else // otherwise entry = B; // next intersection must be an entry do { if( c->isIntersect() ) { c->setEntry( entry ); entry = !entry; } c = c->Next(); } while( c->id() != polyB->m_first->id() ); /* ** Phase 3 of the algorithm is to scan the linked lists of the ** two input polygons an construct a linked list of result ** polygons. We start at the first intersection then depending ** on whether it is an entry or exit point we continue building ** our result polygon by following the source or clip polygon ** either forwards or backwards. */ TRACE( "boolean...phase 3\n" ); while( this->unckd_remain() ) // Loop while unchecked intersections remain { vertex* v = first_unckd_intersect(); // Get the first unchecked intersect point polygon* r = new polygon; // Create a new instance of that class do { v->setChecked(); // Set checked flag true for this intersection if( v->isEntry() ) { do { v = v->Next(); vertex* nv = new vertex( v->X(), v->Y(), v->Xc(), v->Yc(), v->d() ); r->add( nv ); } while( !v->isIntersect() ); } else { do { v = v->Prev(); vertex* nv = new vertex( v->X(), v->Y(), v->Xc( FALSE ), v->Yc( FALSE ), v->d( FALSE ) ); r->add( nv ); } while( !v->isIntersect() ); } v = v->Neighbor(); } while( !v->isChecked() ); // until polygon closed if( last ) // Check in case first time thru the loop r->m_first->setNextPoly( last ); // Save ref to the last poly in the first vertex // of this poly last = r; // Save this polygon } // end of while there is another intersection to check /* ** Clean up the input polygons by deleting the intersection points */ res(); polyB->res(); /* ** It is possible that no intersection between the polygons was found and ** there is no result to return. In this case we make function fail ** gracefully as follows (depending on the requested operation): ** ** A|B : Return this with polyB in m_first->nextPoly ** A&B : Return this ** A\B : Return this ** B\A : return polyB */ polygon* p; if( !last ) { TRACE( "boolean...end with no intersection\n" ); switch( oper ) { case A_OR_B: last = copy_poly(); p = polyB->copy_poly(); last->m_first->setNextPoly( p ); break; case A_AND_B: last = copy_poly(); break; case A_MINUS_B: last = copy_poly(); break; case B_MINUS_A: last = polyB->copy_poly(); break; default: last = copy_poly(); break; } } else if( m_first->m_nextPoly ) { TRACE( "boolean...end with nextPoly\n" ); last->m_first->m_nextPoly = m_first->NextPoly(); } vertex * curr_vertex = last->getFirst(); for( int ii = 0; ii < last->m_cnt; ii++ ) { int x = (int) curr_vertex->X(); int y = (int) curr_vertex->Y(); TRACE( "point %d @ %.4f %.4f\n", ii, (float)x/10000, (float)y/10000 ); curr_vertex = curr_vertex->Next(); } return last; } // end of boolean function /* ** Test if a polygon lies entirly inside this polygon ** ** First every point in the polygon is tested to determine if it is ** inside this polygon. If all points are inside, then the second ** test is performed that looks for any intersections between the ** two polygons. If no intersections are found then the polygon ** must be completely enclosed by this polygon. */ #if 0 function polygon::isPolyInside( p ) { inside = TRUE; c = p->getFirst(); // Get the first vertex in polygon p do { if( !this->isInside( c ) ) // If vertex is NOT inside this polygon inside = FALSE; // then set flag to false c = c->Next(); // Get the next vertex in polygon p } while( c->id() != p->first->id() ); if( inside ) { c = p->getFirst(); // Get the first vertex in polygon p s = getFirst(); // Get the first vertex in this polygon do { do { if( this->ints( s, s->Next(), c, c->Next(), n, x, y, aS, aC ) ) inside = FALSE; c = c->Next(); } while( c->id() != p->first->id() ); s = s->Next(); } while( s->id() != m_first->id() ); } return inside; } // end of isPolyInside /* ** Move Polygon ** ** Translates polygon by delta X and delta Y */ function polygon::move( dx, dy ) { v = getFirst(); do { v->setX( v->X() + dx ); v->setY( v->Y() + dy ); if( v->d() != 0 ) { v->setXc( v->Xc() + dx ); v->setYc( v->Yc() + dy ); } v = v->Next(); } while( v->id() != m_first->id() ); } // end of move polygon /* ** Rotate Polygon ** ** Rotates a polgon about point xr/yr by a radians */ function polygon::rotate( xr, yr, a ) { this->move( -xr, -yr ); // Move the polygon so that the point of // rotation is at the origin (0,0) if( a < 0 ) // We might be passed a negitive angle a += 2 * pi(); // make it positive v = m_first; do { x = v->X(); y = v->Y(); v->setX( x * cos( a ) - y * sin( a ) ); // x' = xCos(a)-ySin(a) v->setY( x * sin( a ) + y * cos( a ) ); // y' = xSin(a)+yCos(a) if( v->d() != 0 ) { x = v->Xc(); y = v->Yc(); v->setXc( x * cos( a ) - y * sin( a ) ); v->setYc( x * sin( a ) + y * cos( a ) ); } v = v->Next(); } while( v->id() != m_first->id() ); this->move( xr, yr ); // Move the rotated polygon back } // end of rotate polygon /* ** Return Bounding Rectangle for a Polygon ** ** returns a polygon object that represents the bounding rectangle ** for this polygon. Arc segments are correctly handled. */ function polygon::& bRect() { minX = INF; minY = INF; maxX = -INF; maxY = -INF; v = m_first; do { if( v->d() != 0 ) // Is it an arc segment { vn = v->Next(); // end vertex of the arc segment v1 = new vertex( v->Xc(), -infinity ); // bottom point of vertical line thru arc center v2 = new vertex( v->Xc(), +infinity ); // top point of vertical line thru arc center if( this->ints( v, vn, v1, v2, n, x, y, aS, aC ) ) // Does line intersect the arc ? { for( i = 0; iY() ); maxY = max( maxY, y[i], v->Y() ); } } else // There was no intersection so bounding rect is determined { // by the start point only, not teh edge of the arc minY = min( minY, v->Y() ); maxY = max( maxY, v->Y() ); } v1 = NULL; v2 = NULL; // Free the memory used h1 = new vertex( -infinity, v->Yc() ); // left point of horozontal line thru arc center h2 = new vertex( +infinity, v->Yc() ); // right point of horozontal line thru arc center if( this->ints( v, vn, h1, h2, n, x, y, aS, aC ) ) // Does line intersect the arc ? { for( i = 0; iX() ); maxX = max( maxX, x[i], v->X() ); } } else { minX = min( minX, v->X() ); maxX = max( maxX, v->X() ); } h1 = NULL; h2 = NULL; } else // Straight segment so just check the vertex { minX = min( minX, v->X() ); minY = min( minY, v->Y() ); maxX = max( maxX, v->X() ); maxY = max( maxY, v->Y() ); } v = v->Next(); } while( v->id() != m_first->id() ); // // Now create an return a polygon with the bounding rectangle // this_class = get_class( this ); // Findout the class I'm in (might be an extension of polygon) p = new this_class; // Create a new instance of that class p->addv( minX, minY ); p->addv( minX, maxY ); p->addv( maxX, maxY ); p->addv( maxX, minY ); return p; } // end of bounding rectangle #endif