You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							649 lines
						
					
					
						
							20 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							649 lines
						
					
					
						
							20 KiB
						
					
					
				| /* | |
|  * This program source code file is part of KiCad, a free EDA CAD application. | |
|  * | |
|  * Modifications Copyright (C) 2018 KiCad Developers | |
|  * | |
|  * This program 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; either version 2 | |
|  * of the License, or (at your option) any later version. | |
|  * | |
|  * 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. | |
|  * | |
|  * You should have received a copy of the GNU General Public License | |
|  * along with this program; if not, you may find one here: | |
|  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html | |
|  * or you may search the http://www.gnu.org website for the version 2 license, | |
|  * or you may write to the Free Software Foundation, Inc., | |
|  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA | |
|  * | |
|  * Based on Uniform Plane Subdivision algorithm from Lamot, Marko, and Borut Žalik. | |
|  * "A fast polygon triangulation algorithm based on uniform plane subdivision." | |
|  * Computers & graphics 27, no. 2 (2003): 239-253. | |
|  * | |
|  * Code derived from: | |
|  * K-3D which is Copyright (c) 2005-2006, Romain Behar, GPL-2, license above | |
|  * earcut which is Copyright (c) 2016, Mapbox, ISC | |
|  * | |
|  * ISC License: | |
|  * Permission to use, copy, modify, and/or distribute this software for any purpose | |
|  * with or without fee is hereby granted, provided that the above copyright notice | |
|  * and this permission notice appear in all copies. | |
|  * | |
|  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | |
|  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
|  * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | |
|  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS | |
|  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | |
|  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF | |
|  * THIS SOFTWARE. | |
|  * | |
|  */ | |
| 
 | |
| #ifndef __POLYGON_TRIANGULATION_H | |
| #define __POLYGON_TRIANGULATION_H | |
|  | |
| #include <algorithm> | |
| #include <cmath> | |
| #include <vector> | |
| #include <math/box2.h> | |
|  | |
| #include "clipper.hpp" | |
|  | |
| class PolygonTriangulation | |
| { | |
| 
 | |
| public: | |
| 
 | |
|     PolygonTriangulation( SHAPE_POLY_SET::TRIANGULATED_POLYGON& aResult ) : | |
|         m_result( aResult ) | |
|     {}; | |
| 
 | |
| private: | |
|     struct Vertex | |
|     { | |
|         Vertex( size_t aIndex, double aX, double aY, PolygonTriangulation* aParent ) : | |
|                 i( aIndex ), x( aX ), y( aY ), parent( aParent ) | |
|         { | |
|         } | |
|         Vertex& operator=( const Vertex& ) = delete; | |
|         Vertex& operator=( Vertex&& ) = delete; | |
| 
 | |
|         bool operator==( const Vertex& rhs ) const | |
|         { | |
|             return this->x == rhs.x && this->y == rhs.y; | |
|         } | |
|         bool operator!=( const Vertex& rhs ) const { return !( *this == rhs ); } | |
| 
 | |
| 
 | |
|         /** | |
|          * Function split | |
|          * Splits the referenced polygon between the reference point and | |
|          * vertex b, assuming they are in the same polygon.  Notes that while we | |
|          * create a new vertex pointer for the linked list, we maintain the same | |
|          * vertex index value from the original polygon.  In this way, we have | |
|          * two polygons that both share the same vertices. | |
|          * | |
|          * Returns the pointer to the newly created vertex in the polygon that | |
|          * does not include the reference vertex. | |
|          */ | |
|         Vertex* split( Vertex* b ) | |
|         { | |
|             parent->m_vertices.emplace_back( i, x, y, parent ); | |
|             Vertex* a2 = &parent->m_vertices.back(); | |
|             parent->m_vertices.emplace_back( b->i, b->x, b->y, parent ); | |
|             Vertex* b2 = &parent->m_vertices.back(); | |
|             Vertex* an = next; | |
|             Vertex* bp = b->prev; | |
| 
 | |
|             next = b; | |
|             b->prev = this; | |
| 
 | |
|             a2->next = an; | |
|             an->prev = a2; | |
| 
 | |
|             b2->next = a2; | |
|             a2->prev = b2; | |
| 
 | |
|             bp->next = b2; | |
|             b2->prev = bp; | |
| 
 | |
|             return b2; | |
|         } | |
| 
 | |
|         /** | |
|          * Function remove | |
|          * Removes the node from the linked list and z-ordered linked list. | |
|          */ | |
|         void remove() | |
|         { | |
|             next->prev = prev; | |
|             prev->next = next; | |
| 
 | |
|             if( prevZ ) | |
|                 prevZ->nextZ = nextZ; | |
|             if( nextZ ) | |
|                 nextZ->prevZ = prevZ; | |
|             next = NULL; | |
|             prev = NULL; | |
|             nextZ = NULL; | |
|             prevZ = NULL; | |
|         } | |
| 
 | |
| 
 | |
|         void updateOrder() | |
|         { | |
|             if( !z ) | |
|                 z = parent->zOrder( x, y ); | |
|         } | |
| 
 | |
|         /** | |
|          * Function updateList | |
|          * After inserting or changing nodes, this function should be called to | |
|          * remove duplicate vertices and ensure z-ordering is correct | |
|          */ | |
|         void updateList() | |
|         { | |
|             Vertex* p = next; | |
| 
 | |
|             while( p != this ) | |
|             { | |
|                 /** | |
|                  * Remove duplicates | |
|                  */ | |
|                 if( *p == *p->next ) | |
|                 { | |
|                     p = p->prev; | |
|                     p->next->remove(); | |
| 
 | |
|                     if( p == p->next ) | |
|                         break; | |
|                 } | |
| 
 | |
|                 p->updateOrder(); | |
|                 p = p->next; | |
|             }; | |
| 
 | |
|             updateOrder(); | |
|             zSort(); | |
|         } | |
| 
 | |
|         /** | |
|          * Sort all vertices in this vertex's list by their Morton code | |
|          */ | |
|         void zSort() | |
|         { | |
|             std::deque<Vertex*> queue; | |
| 
 | |
|             queue.push_back( this ); | |
| 
 | |
|             for( auto p = next; p && p != this; p = p->next ) | |
|                 queue.push_back( p ); | |
| 
 | |
|             std::sort( queue.begin(), queue.end(), []( const Vertex* a, const Vertex* b) | |
|             { | |
|                 return a->z < b->z; | |
|             } ); | |
| 
 | |
|             Vertex* prev_elem = nullptr; | |
|             for( auto elem : queue ) | |
|             { | |
|                 if( prev_elem ) | |
|                     prev_elem->nextZ = elem; | |
| 
 | |
|                 elem->prevZ = prev_elem; | |
|                 prev_elem = elem; | |
|             } | |
| 
 | |
|             prev_elem->nextZ = nullptr; | |
|         } | |
| 
 | |
| 
 | |
|         /** | |
|          * Check to see if triangle surrounds our current vertex | |
|          */ | |
|         bool inTriangle( const Vertex& a, const Vertex& b, const Vertex& c ) | |
|         { | |
|             return     ( c.x - x ) * ( a.y - y ) - ( a.x - x ) * ( c.y - y ) >= 0 | |
|                     && ( a.x - x ) * ( b.y - y ) - ( b.x - x ) * ( a.y - y ) >= 0 | |
|                     && ( b.x - x ) * ( c.y - y ) - ( c.x - x ) * ( b.y - y ) >= 0; | |
|         } | |
| 
 | |
|         const size_t i; | |
|         const double x; | |
|         const double y; | |
|         PolygonTriangulation* parent; | |
| 
 | |
|         // previous and next vertices nodes in a polygon ring | |
|         Vertex* prev = nullptr; | |
|         Vertex* next = nullptr; | |
| 
 | |
|         // z-order curve value | |
|         int32_t z = 0; | |
| 
 | |
|         // previous and next nodes in z-order | |
|         Vertex* prevZ = nullptr; | |
|         Vertex* nextZ = nullptr; | |
|     }; | |
| 
 | |
|     BOX2I m_bbox; | |
|     std::deque<Vertex> m_vertices; | |
|     SHAPE_POLY_SET::TRIANGULATED_POLYGON& m_result; | |
| 
 | |
|     /** | |
|      * Calculate the Morton code of the Vertex | |
|      * http://www.graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN | |
|      * | |
|      */ | |
|     int32_t zOrder( const double aX, const double aY ) const | |
|     { | |
|         int32_t x = static_cast<int32_t>( 32767.0 * ( aX - m_bbox.GetX() ) / m_bbox.GetWidth() ); | |
|         int32_t y = static_cast<int32_t>( 32767.0 * ( aY - m_bbox.GetY() ) / m_bbox.GetHeight() ); | |
| 
 | |
|         x = ( x | ( x << 8 ) ) & 0x00FF00FF; | |
|         x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; | |
|         x = ( x | ( x << 2 ) ) & 0x33333333; | |
|         x = ( x | ( x << 1 ) ) & 0x55555555; | |
| 
 | |
|         y = ( y | ( y << 8 ) ) & 0x00FF00FF; | |
|         y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; | |
|         y = ( y | ( y << 2 ) ) & 0x33333333; | |
|         y = ( y | ( y << 1 ) ) & 0x55555555; | |
| 
 | |
|         return x | ( y << 1 ); | |
|     } | |
| 
 | |
|     /** | |
|      * Function removeNullTriangles | |
|      * Iterates through the list to remove NULL triangles if they exist. | |
|      * This should only be called as a last resort when tesselation fails | |
|      * as the NULL triangles are inserted as Steiner points to improve the | |
|      * triangulation regularity of polygons | |
|      */ | |
|     bool removeNullTriangles( Vertex* aStart ) | |
|     { | |
|         bool retval = false; | |
|         Vertex* p = aStart->next; | |
| 
 | |
|         while( p != aStart ) | |
|         { | |
|             if( area( p->prev, p, p->next ) == 0.0 ) | |
|             { | |
|                 p = p->prev; | |
|                 p->next->remove(); | |
|                 retval = true; | |
| 
 | |
|                 if( p == p->next ) | |
|                     break; | |
|             } | |
|             p = p->next; | |
|         }; | |
| 
 | |
|         // We needed an end point above that wouldn't be removed, so | |
|         // here we do the final check for this as a Steiner point | |
|         if( area( aStart->prev, aStart, aStart->next ) == 0.0 ) | |
|         { | |
|             p->remove(); | |
|             retval = true; | |
|         } | |
| 
 | |
|         return retval; | |
|     } | |
| 
 | |
|     /** | |
|      * Function createList | |
|      * Takes a Clipper path and converts it into a circular, doubly-linked | |
|      * list for triangulation | |
|      */ | |
|     Vertex* createList( const ClipperLib::Path& aPath ) | |
|     { | |
|         Vertex* tail = nullptr; | |
| 
 | |
|         for( auto point : aPath ) | |
|             tail = insertVertex( VECTOR2I( point.X, point.Y ), tail ); | |
| 
 | |
|         if( tail && ( *tail == *tail->next ) ) | |
|         { | |
|             tail->next->remove(); | |
|         } | |
| 
 | |
|         return tail; | |
| 
 | |
|     } | |
| 
 | |
|     /** | |
|      * Function createList | |
|      * Takes the SHAPE_LINE_CHAIN and links each point into a | |
|      * circular, doubly-linked list | |
|      */ | |
|     Vertex* createList( const SHAPE_LINE_CHAIN& points ) | |
|     { | |
|         Vertex* tail = nullptr; | |
| 
 | |
|         for( int i = 0; i < points.PointCount(); i++ ) | |
|             tail = insertVertex( points.CPoint( i ), tail ); | |
| 
 | |
|         if( tail && ( *tail == *tail->next ) ) | |
|         { | |
|             tail->next->remove(); | |
|         } | |
| 
 | |
|         return tail; | |
|     } | |
| 
 | |
|     /** | |
|      * Function: earcutList | |
|      * Walks through a circular linked list starting at aPoint.  For each point, | |
|      * test to see if the adjacent points form a triangle that is completely enclosed | |
|      * by the remaining polygon (an "ear" sticking off the polygon).  If the three points | |
|      * form an ear, we log the ear's location and remove the center point from the linked list. | |
|      * | |
|      * This function can be called recursively in the case of difficult polygons.  In cases where | |
|      * there is an intersection (not technically allowed by KiCad, but could exist in an edited file), | |
|      * we create a single triangle and remove both vertices before attempting to | |
|      */ | |
|     bool earcutList( Vertex* aPoint, int pass = 0 ) | |
|     { | |
|         if( !aPoint ) | |
|             return true; | |
| 
 | |
|         Vertex* stop = aPoint; | |
|         Vertex* prev; | |
|         Vertex* next; | |
| 
 | |
|         while( aPoint->prev != aPoint->next ) | |
|         { | |
|             prev = aPoint->prev; | |
|             next = aPoint->next; | |
| 
 | |
|             if( isEar( aPoint ) ) | |
|             { | |
|                 m_result.AddTriangle( prev->i, aPoint->i, next->i ); | |
|                 aPoint->remove(); | |
| 
 | |
|                 // Skip one vertex as the triangle will account for the prev node | |
|                 aPoint = next->next; | |
|                 stop = next->next; | |
| 
 | |
|                 continue; | |
|             } | |
| 
 | |
|             Vertex* nextNext = next->next; | |
| 
 | |
|             if( *prev != *nextNext && intersects( prev, aPoint, next, nextNext ) && | |
|                     locallyInside( prev, nextNext ) && | |
|                     locallyInside( nextNext, prev ) ) | |
|             { | |
|                 m_result.AddTriangle( prev->i, aPoint->i, nextNext->i ); | |
| 
 | |
|                 // remove two nodes involved | |
|                 next->remove(); | |
|                 aPoint->remove(); | |
| 
 | |
|                 aPoint = nextNext; | |
| 
 | |
|                 continue; | |
|             } | |
| 
 | |
|             aPoint = next; | |
| 
 | |
|             /** | |
|              * We've searched the entire polygon for available ears and there are still un-sliced nodes | |
|              * remaining | |
|              */ | |
|             if( aPoint == stop ) | |
|             { | |
|                 // First, try to remove the remaining steiner points | |
|                 if( removeNullTriangles( aPoint ) ) | |
|                     continue; | |
| 
 | |
|                 // If we don't have any NULL triangles left, cut the polygon in two and try again | |
|                 splitPolygon( aPoint ); | |
|                 break; | |
|             } | |
|         } | |
| 
 | |
|         /** | |
|          * At this point, our polygon should be fully tesselated. | |
|          */ | |
|         return( aPoint->prev == aPoint->next ); | |
|     } | |
| 
 | |
|     /** | |
|      * Function isEar | |
|      * Checks whether the given vertex is in the middle of an ear. | |
|      * This works by walking forward and backward in zOrder to the limits | |
|      * of the minimal bounding box formed around the triangle, checking whether | |
|      * any points are located inside the given triangle. | |
|      * | |
|      * Returns true if aEar is the apex point of a ear in the polygon | |
|      */ | |
|     bool isEar( Vertex* aEar ) const | |
|     { | |
|         const Vertex* a = aEar->prev; | |
|         const Vertex* b = aEar; | |
|         const Vertex* c = aEar->next; | |
| 
 | |
|         // If the area >=0, then the three points for a concave sequence | |
|         // with b as the reflex point | |
|         if( area( a, b, c ) >= 0 ) | |
|             return false; | |
| 
 | |
|         // triangle bbox | |
|         const double minTX = std::min( a->x, std::min( b->x, c->x ) ); | |
|         const double minTY = std::min( a->y, std::min( b->y, c->y ) ); | |
|         const double maxTX = std::max( a->x, std::max( b->x, c->x ) ); | |
|         const double maxTY = std::max( a->y, std::max( b->y, c->y ) ); | |
| 
 | |
|         // z-order range for the current triangle bounding box | |
|         const int32_t minZ = zOrder( minTX, minTY ); | |
|         const int32_t maxZ = zOrder( maxTX, maxTY ); | |
| 
 | |
|         // first look for points inside the triangle in increasing z-order | |
|         Vertex* p = aEar->nextZ; | |
| 
 | |
|         while( p && p->z <= maxZ ) | |
|         { | |
|             if( p != a && p != c | |
|                     && p->inTriangle( *a, *b, *c ) | |
|                     && area( p->prev, p, p->next ) >= 0 ) | |
|                 return false; | |
|             p = p->nextZ; | |
|         } | |
| 
 | |
|         // then look for points in decreasing z-order | |
|         p = aEar->prevZ; | |
| 
 | |
|         while( p && p->z >= minZ ) | |
|         { | |
|             if( p != a && p != c | |
|                     && p->inTriangle( *a, *b, *c ) | |
|                     && area( p->prev, p, p->next ) >= 0 ) | |
|                 return false; | |
|             p = p->prevZ; | |
|         } | |
| 
 | |
|         return true; | |
|     } | |
| 
 | |
|     /** | |
|      * Function splitPolygon | |
|      * If we cannot find an ear to slice in the current polygon list, we | |
|      * use this to split the polygon into two separate lists and slice them each | |
|      * independently.  This is assured to generate at least one new ear if the | |
|      * split is successful | |
|      */ | |
|     void splitPolygon( Vertex* start ) | |
|     { | |
|         Vertex* origPoly = start; | |
|         do | |
|         { | |
|             Vertex* marker = origPoly->next->next; | |
|             while( marker != origPoly->prev ) | |
|             { | |
|                 // Find a diagonal line that is wholly enclosed by the polygon interior | |
|                 if( origPoly->i != marker->i && goodSplit( origPoly, marker ) ) | |
|                 { | |
|                     Vertex* newPoly = origPoly->split( marker ); | |
| 
 | |
|                     origPoly->updateList(); | |
|                     newPoly->updateList(); | |
| 
 | |
|                     earcutList( origPoly ); | |
|                     earcutList( newPoly ); | |
|                     return; | |
|                 } | |
|                 marker = marker->next; | |
|             } | |
|             origPoly = origPoly->next; | |
|         } while( origPoly != start ); | |
|     } | |
| 
 | |
|     /** | |
|      * Check if a segment joining two vertices lies fully inside the polygon. | |
|      * To do this, we first ensure that the line isn't along the polygon edge. | |
|      * Next, we know that if the line doesn't intersect the polygon, then it is | |
|      * either fully inside or fully outside the polygon.  Finally, by checking whether | |
|      * the segment is enclosed by the local triangles, we distinguish between | |
|      * these two cases and no further checks are needed. | |
|      */ | |
|     bool goodSplit( const Vertex* a, const Vertex* b ) const | |
|     { | |
|         return a->next->i != b->i && | |
|                a->prev->i != b->i && | |
|                !intersectsPolygon( a, b ) && | |
|                locallyInside( a, b ); | |
|     } | |
| 
 | |
|     /** | |
|      * Function area | |
|      * Returns the twice the signed area of the triangle formed by vertices | |
|      * p, q, r. | |
|      */ | |
|     double area( const Vertex* p, const Vertex* q, const Vertex* r ) const | |
|     { | |
|         return ( q->y - p->y ) * ( r->x - q->x ) - ( q->x - p->x ) * ( r->y - q->y ); | |
|     } | |
| 
 | |
|     /** | |
|      * Function intersects | |
|      * Checks for intersection between two segments, end points included. | |
|      * Returns true if p1-p2 intersects q1-q2 | |
|      */ | |
|     bool intersects( const Vertex* p1, const Vertex* q1, const Vertex* p2, const Vertex* q2 ) const | |
|     { | |
|         if( ( *p1 == *q1 && *p2 == *q2 ) || ( *p1 == *q2 && *p2 == *q1 ) ) | |
|             return true; | |
| 
 | |
|         return ( area( p1, q1, p2 ) > 0 ) != ( area( p1, q1, q2 ) > 0 ) | |
|                 && ( area( p2, q2, p1 ) > 0 ) != ( area( p2, q2, q1 ) > 0 ); | |
|     } | |
| 
 | |
|     /** | |
|      * Function intersectsPolygon | |
|      * Checks whether the segment from vertex a -> vertex b crosses any of the segments | |
|      * of the polygon of which vertex a is a member. | |
|      * Return true if the segment intersects the edge of the polygon | |
|      */ | |
|     bool intersectsPolygon( const Vertex* a, const Vertex* b ) const | |
|     { | |
|         const Vertex* p = a->next; | |
|         do | |
|         { | |
|             if( p->i != a->i && | |
|                 p->next->i != a->i && | |
|                 p->i != b->i && | |
|                 p->next->i != b->i && intersects( p, p->next, a, b ) ) | |
|                 return true; | |
| 
 | |
|             p = p->next; | |
|         } while( p != a ); | |
| 
 | |
|         return false; | |
|     } | |
| 
 | |
|     /** | |
|      * Function locallyInside | |
|      * Checks whether the segment from vertex a -> vertex b is inside the polygon | |
|      * around the immediate area of vertex a.  We don't define the exact area | |
|      * over which the segment is inside but it is guaranteed to be inside the polygon | |
|      * immediately adjacent to vertex a. | |
|      * Returns true if the segment from a->b is inside a's polygon next to vertex a | |
|      */ | |
|     bool locallyInside( const Vertex* a, const Vertex* b ) const | |
|     { | |
|         if( area( a->prev, a, a->next ) < 0 ) | |
|             return area( a, b, a->next ) >= 0 && area( a, a->prev, b ) >= 0; | |
|         else | |
|             return area( a, b, a->prev ) < 0 || area( a, a->next, b ) < 0; | |
|     } | |
| 
 | |
|     /** | |
|      * Function insertVertex | |
|      * Creates an entry in the vertices lookup and optionally inserts the newly | |
|      * created vertex into an existing linked list. | |
|      * Returns a pointer to the newly created vertex | |
|      */ | |
|     Vertex* insertVertex( const VECTOR2I& pt, Vertex* last ) | |
|     { | |
|         m_result.AddVertex( pt ); | |
|         m_vertices.emplace_back( m_result.GetVertexCount() - 1, pt.x, pt.y, this ); | |
| 
 | |
|         Vertex* p = &m_vertices.back(); | |
|         if( !last ) | |
|         { | |
|             p->prev = p; | |
|             p->next = p; | |
|         } | |
|         else | |
|         { | |
|             p->next = last->next; | |
|             p->prev = last; | |
|             last->next->prev = p; | |
|             last->next = p; | |
|         } | |
|         return p; | |
|     } | |
| 
 | |
| 
 | |
| public: | |
| 
 | |
|     void TesselatePolygon( const SHAPE_LINE_CHAIN& aPoly ) | |
|     { | |
|         ClipperLib::Clipper c; | |
|         m_bbox = aPoly.BBox(); | |
| 
 | |
|         if( !m_bbox.GetWidth() || !m_bbox.GetHeight() ) | |
|             return; | |
| 
 | |
|         Vertex* outerNode = createList( aPoly ); | |
|         if( !outerNode ) | |
|             return; | |
| 
 | |
|         outerNode->updateList(); | |
|         if( !earcutList( outerNode ) ) | |
|         { | |
|             m_vertices.clear(); | |
|             m_result.Clear(); | |
| 
 | |
|             ClipperLib::Paths simplified; | |
|             ClipperLib::SimplifyPolygon( aPoly.convertToClipper( true ), simplified ); | |
| 
 | |
|             for( auto path : simplified ) | |
|             { | |
|                 outerNode = createList( path ); | |
|                 if( !outerNode ) | |
|                     return; | |
| 
 | |
|                 earcutList( outerNode ); | |
|             } | |
|         } | |
| 
 | |
|         m_vertices.clear(); | |
|     } | |
| }; | |
| 
 | |
| #endif //__POLYGON_TRIANGULATION_H
 |