/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015 CERN * @author Tomasz Wlostowski * * Point in polygon algorithm adapted from Clipper Library (C) Angus Johnson, * subject to Clipper library license. * * 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 */ #include #include #include #include #include #include #include #include using namespace ClipperLib; SHAPE_POLY_SET::SHAPE_POLY_SET() : SHAPE( SH_POLY_SET ) { } SHAPE_POLY_SET::~SHAPE_POLY_SET() { } int SHAPE_POLY_SET::NewOutline() { SHAPE_LINE_CHAIN empty_path; POLYGON poly; poly.push_back( empty_path ); m_polys.push_back( poly ); return m_polys.size() - 1; } int SHAPE_POLY_SET::NewHole( int aOutline ) { m_polys.back().push_back( SHAPE_LINE_CHAIN() ); return m_polys.back().size() - 2; } int SHAPE_POLY_SET::Append( int x, int y, int aOutline, int aHole ) { if( aOutline < 0 ) aOutline += m_polys.size(); int idx; if( aHole < 0 ) idx = 0; else idx = aHole + 1; assert( aOutline < (int)m_polys.size() ); assert( idx < (int)m_polys[aOutline].size() ); m_polys[aOutline][idx].Append( x, y ); return m_polys[aOutline][idx].PointCount(); } int SHAPE_POLY_SET::VertexCount( int aOutline , int aHole ) const { if( aOutline < 0 ) aOutline += m_polys.size(); int idx; if( aHole < 0 ) idx = 0; else idx = aHole + 1; assert ( aOutline < (int)m_polys.size() ); assert ( idx < (int)m_polys[aOutline].size() ); return m_polys[aOutline][idx].PointCount(); } const VECTOR2I& SHAPE_POLY_SET::CVertex( int index, int aOutline , int aHole ) const { if( aOutline < 0 ) aOutline += m_polys.size(); int idx; if( aHole < 0 ) idx = 0; else idx = aHole + 1; assert( aOutline < (int)m_polys.size() ); assert( idx < (int)m_polys[aOutline].size() ); return m_polys[aOutline][idx].CPoint( index ); } VECTOR2I& SHAPE_POLY_SET::Vertex( int index, int aOutline , int aHole ) { if( aOutline < 0 ) aOutline += m_polys.size(); int idx; if( aHole < 0 ) idx = 0; else idx = aHole + 1; assert( aOutline < (int)m_polys.size() ); assert( idx < (int)m_polys[aOutline].size() ); return m_polys[aOutline][idx].Point( index ); } int SHAPE_POLY_SET::AddOutline( const SHAPE_LINE_CHAIN& aOutline ) { assert( aOutline.IsClosed() ); POLYGON poly; poly.push_back( aOutline ); m_polys.push_back( poly ); return m_polys.size() - 1; } int SHAPE_POLY_SET::AddHole( const SHAPE_LINE_CHAIN& aHole, int aOutline ) { assert ( m_polys.size() ); if( aOutline < 0 ) aOutline += m_polys.size(); POLYGON& poly = m_polys[aOutline]; assert( poly.size() ); poly.push_back( aHole ); return poly.size() - 1; } const Path SHAPE_POLY_SET::convertToClipper( const SHAPE_LINE_CHAIN& aPath, bool aRequiredOrientation ) { Path c_path; for( int i = 0; i < aPath.PointCount(); i++ ) { const VECTOR2I& vertex = aPath.CPoint( i ); c_path.push_back( IntPoint( vertex.x, vertex.y ) ); } if( Orientation( c_path ) != aRequiredOrientation ) ReversePath( c_path ); return c_path; } const SHAPE_LINE_CHAIN SHAPE_POLY_SET::convertFromClipper( const Path& aPath ) { SHAPE_LINE_CHAIN lc; for( unsigned int i = 0; i < aPath.size(); i++ ) lc.Append( aPath[i].X, aPath[i].Y ); return lc; } void SHAPE_POLY_SET::booleanOp( ClipType aType, const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode ) { Clipper c; if( aFastMode == PM_STRICTLY_SIMPLE ) c.StrictlySimple( true ); for( const POLYGON& poly : m_polys ) { for( unsigned int i = 0; i < poly.size(); i++ ) c.AddPath( convertToClipper( poly[i], i > 0 ? false : true ), ptSubject, true ); } for( const POLYGON& poly : aOtherShape.m_polys ) { for( unsigned int i = 0; i < poly.size(); i++ ) c.AddPath( convertToClipper( poly[i], i > 0 ? false : true ), ptClip, true ); } PolyTree solution; c.Execute( aType, solution, pftNonZero, pftNonZero ); importTree( &solution ); } void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aShape, const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode ) { Clipper c; if( aFastMode == PM_STRICTLY_SIMPLE ) c.StrictlySimple( true ); for( const POLYGON& poly : aShape.m_polys ) { for( unsigned int i = 0; i < poly.size(); i++ ) c.AddPath( convertToClipper( poly[i], i > 0 ? false : true ), ptSubject, true ); } for( const POLYGON& poly : aOtherShape.m_polys ) { for( unsigned int i = 0; i < poly.size(); i++ ) c.AddPath( convertToClipper( poly[i], i > 0 ? false : true ), ptClip, true ); } PolyTree solution; c.Execute( aType, solution, pftNonZero, pftNonZero ); importTree( &solution ); } void SHAPE_POLY_SET::BooleanAdd( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ) { booleanOp( ctUnion, b, aFastMode ); } void SHAPE_POLY_SET::BooleanSubtract( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ) { booleanOp( ctDifference, b, aFastMode ); } void SHAPE_POLY_SET::BooleanIntersection( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ) { booleanOp( ctIntersection, b, aFastMode ); } void SHAPE_POLY_SET::BooleanAdd( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ) { booleanOp( ctUnion, a, b, aFastMode ); } void SHAPE_POLY_SET::BooleanSubtract( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ) { booleanOp( ctDifference, a, b, aFastMode ); } void SHAPE_POLY_SET::BooleanIntersection( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ) { booleanOp( ctIntersection, a, b, aFastMode ); } void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount ) { // A static table to avoid repetitive calculations of the coefficient // 1.0 - cos( M_PI/aCircleSegmentsCount) // aCircleSegmentsCount is most of time <= 64 and usually 8, 12, 16, 32 #define SEG_CNT_MAX 64 static double arc_tolerance_factor[SEG_CNT_MAX+1]; ClipperOffset c; for( const POLYGON& poly : m_polys ) { for( unsigned int i = 0; i < poly.size(); i++ ) c.AddPath( convertToClipper( poly[i], i > 0 ? false : true ), jtRound, etClosedPolygon ); } PolyTree solution; // Calculate the arc tolerance (arc error) from the seg count by circle. // the seg count is nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aFactor)) // see: // www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm if( aCircleSegmentsCount < 6 ) // avoid incorrect aCircleSegmentsCount values aCircleSegmentsCount = 6; double coeff; if( aCircleSegmentsCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegmentsCount] == 0 ) { coeff = 1.0 - cos( M_PI/aCircleSegmentsCount); if( aCircleSegmentsCount <= SEG_CNT_MAX ) arc_tolerance_factor[aCircleSegmentsCount] = coeff; } else coeff = arc_tolerance_factor[aCircleSegmentsCount]; c.ArcTolerance = std::abs( aFactor ) * coeff; c.Execute( solution, aFactor ); importTree( &solution ); } void SHAPE_POLY_SET::importTree( PolyTree* tree ) { m_polys.clear(); for( PolyNode* n = tree->GetFirst(); n; n = n->GetNext() ) { if( !n->IsHole() ) { POLYGON paths; paths.reserve( n->Childs.size() + 1 ); paths.push_back( convertFromClipper( n->Contour ) ); for( unsigned int i = 0; i < n->Childs.size(); i++ ) paths.push_back( convertFromClipper( n->Childs[i]->Contour ) ); m_polys.push_back(paths); } } } // Polygon fracturing code. Work in progress. struct FractureEdge { FractureEdge( bool connected, SHAPE_LINE_CHAIN* owner, int index ) : m_connected( connected ), m_next( NULL ) { m_p1 = owner->CPoint( index ); m_p2 = owner->CPoint( index + 1 ); } FractureEdge( int y = 0 ) : m_connected( false ), m_next( NULL ) { m_p1.x = m_p2.y = y; } FractureEdge( bool connected, const VECTOR2I& p1, const VECTOR2I& p2 ) : m_connected( connected ), m_p1( p1 ), m_p2( p2 ), m_next( NULL ) { } bool matches( int y ) const { int y_min = std::min( m_p1.y, m_p2.y ); int y_max = std::max( m_p1.y, m_p2.y ); return ( y >= y_min ) && ( y <= y_max ); } bool m_connected; VECTOR2I m_p1, m_p2; FractureEdge* m_next; }; typedef std::vector FractureEdgeSet; static int processEdge( FractureEdgeSet& edges, FractureEdge* edge ) { int x = edge->m_p1.x; int y = edge->m_p1.y; int min_dist = std::numeric_limits::max(); int x_nearest = 0; FractureEdge* e_nearest = NULL; for( FractureEdgeSet::iterator i = edges.begin(); i != edges.end(); ++i ) { if( !(*i)->matches( y ) ) continue; int x_intersect; if( (*i)->m_p1.y == (*i)->m_p2.y ) // horizontal edge x_intersect = std::max ( (*i)->m_p1.x, (*i)->m_p2.x ); else x_intersect = (*i)->m_p1.x + rescale((*i)->m_p2.x - (*i)->m_p1.x, y - (*i)->m_p1.y, (*i)->m_p2.y - (*i)->m_p1.y ); int dist = ( x - x_intersect ); if( dist >= 0 && dist < min_dist && (*i)->m_connected ) { min_dist = dist; x_nearest = x_intersect; e_nearest = (*i); } } if( e_nearest && e_nearest->m_connected ) { int count = 0; FractureEdge* lead1 = new FractureEdge( true, VECTOR2I( x_nearest, y ), VECTOR2I( x, y ) ); FractureEdge* lead2 = new FractureEdge( true, VECTOR2I( x, y ), VECTOR2I( x_nearest, y ) ); FractureEdge* split_2 = new FractureEdge( true, VECTOR2I( x_nearest, y ), e_nearest->m_p2 ); edges.push_back( split_2 ); edges.push_back( lead1 ); edges.push_back( lead2 ); FractureEdge* link = e_nearest->m_next; e_nearest->m_p2 = VECTOR2I( x_nearest, y ); e_nearest->m_next = lead1; lead1->m_next = edge; FractureEdge*last; for( last = edge; last->m_next != edge; last = last->m_next ) { last->m_connected = true; count++; } last->m_connected = true; last->m_next = lead2; lead2->m_next = split_2; split_2->m_next = link; return count + 1; } return 0; } void SHAPE_POLY_SET::fractureSingle( POLYGON& paths ) { FractureEdgeSet edges; FractureEdgeSet border_edges; FractureEdge* root = NULL; bool first = true; if( paths.size() == 1 ) return; int num_unconnected = 0; for( SHAPE_LINE_CHAIN& path : paths ) { int index = 0; FractureEdge *prev = NULL, *first_edge = NULL; int x_min = std::numeric_limits::max(); for( int i = 0; i < path.PointCount(); i++ ) { const VECTOR2I& p = path.CPoint( i ); if( p.x < x_min ) x_min = p.x; } for( int i = 0; i < path.PointCount(); i++ ) { FractureEdge* fe = new FractureEdge( first, &path, index++ ); if( !root ) root = fe; if( !first_edge ) first_edge = fe; if( prev ) prev->m_next = fe; if( i == path.PointCount() - 1 ) fe->m_next = first_edge; prev = fe; edges.push_back( fe ); if( !first ) { if( fe->m_p1.x == x_min ) border_edges.push_back( fe ); } if( !fe->m_connected ) num_unconnected++; } first = false; // first path is always the outline } // keep connecting holes to the main outline, until there's no holes left... while( num_unconnected > 0 ) { int x_min = std::numeric_limits::max(); FractureEdge* smallestX = NULL; // find the left-most hole edge and merge with the outline for( FractureEdgeSet::iterator i = border_edges.begin(); i != border_edges.end(); ++i ) { int xt = (*i)->m_p1.x; if( ( xt < x_min ) && ! (*i)->m_connected ) { x_min = xt; smallestX = *i; } } num_unconnected -= processEdge( edges, smallestX ); } paths.clear(); SHAPE_LINE_CHAIN newPath; newPath.SetClosed( true ); FractureEdge* e; for( e = root; e->m_next != root; e = e->m_next ) newPath.Append( e->m_p1 ); newPath.Append( e->m_p1 ); for( FractureEdgeSet::iterator i = edges.begin(); i != edges.end(); ++i ) delete *i; paths.push_back( newPath ); } void SHAPE_POLY_SET::Fracture( POLYGON_MODE aFastMode ) { Simplify( aFastMode ); // remove overlapping holes/degeneracy for( POLYGON& paths : m_polys ) { fractureSingle( paths ); } } void SHAPE_POLY_SET::Simplify( POLYGON_MODE aFastMode ) { SHAPE_POLY_SET empty; booleanOp( ctUnion, empty, aFastMode ); } const std::string SHAPE_POLY_SET::Format() const { std::stringstream ss; ss << "polyset " << m_polys.size() << "\n"; for( unsigned i = 0; i < m_polys.size(); i++ ) { ss << "poly " << m_polys[i].size() << "\n"; for( unsigned j = 0; j < m_polys[i].size(); j++) { ss << m_polys[i][j].PointCount() << "\n"; for( int v = 0; v < m_polys[i][j].PointCount(); v++) ss << m_polys[i][j].CPoint( v ).x << " " << m_polys[i][j].CPoint( v ).y << "\n"; } ss << "\n"; } return ss.str(); } bool SHAPE_POLY_SET::Parse( std::stringstream& aStream ) { std::string tmp; aStream >> tmp; if( tmp != "polyset" ) return false; aStream >> tmp; int n_polys = atoi( tmp.c_str() ); if( n_polys < 0 ) return false; for( int i = 0; i < n_polys; i++ ) { POLYGON paths; aStream >> tmp; if( tmp != "poly" ) return false; aStream >> tmp; int n_outlines = atoi( tmp.c_str() ); if( n_outlines < 0 ) return false; for( int j = 0; j < n_outlines; j++ ) { SHAPE_LINE_CHAIN outline; outline.SetClosed( true ); aStream >> tmp; int n_vertices = atoi( tmp.c_str() ); for( int v = 0; v < n_vertices; v++ ) { VECTOR2I p; aStream >> tmp; p.x = atoi( tmp.c_str() ); aStream >> tmp; p.y = atoi( tmp.c_str() ); outline.Append( p ); } paths.push_back( outline ); } m_polys.push_back( paths ); } return true; } const BOX2I SHAPE_POLY_SET::BBox( int aClearance ) const { BOX2I bb; for( unsigned i = 0; i < m_polys.size(); i++ ) { if( i == 0 ) bb = m_polys[i][0].BBox(); else bb.Merge( m_polys[i][0].BBox() ); } bb.Inflate( aClearance ); return bb; } void SHAPE_POLY_SET::RemoveAllContours() { m_polys.clear(); } void SHAPE_POLY_SET::DeletePolygon( int aIdx ) { m_polys.erase( m_polys.begin() + aIdx ); } void SHAPE_POLY_SET::Append( const SHAPE_POLY_SET& aSet ) { m_polys.insert( m_polys.end(), aSet.m_polys.begin(), aSet.m_polys.end() ); } void SHAPE_POLY_SET::Append( const VECTOR2I& aP, int aOutline, int aHole ) { Append( aP.x, aP.y, aOutline, aHole ); } bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex ) const { // fixme: support holes! if( m_polys.size() == 0 ) // empty set? return false; if( aSubpolyIndex >= 0 ) return pointInPolygon( aP, m_polys[aSubpolyIndex][0] ); for( const POLYGON& polys : m_polys ) { if( polys.size() == 0 ) continue; if( pointInPolygon( aP, polys[0] ) ) return true; } return false; } bool SHAPE_POLY_SET::pointInPolygon( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aPath ) const { int result = 0; int cnt = aPath.PointCount(); if ( !aPath.BBox().Contains( aP ) ) // test with bounding box first return false; if( cnt < 3 ) return false; VECTOR2I ip = aPath.CPoint( 0 ); for( int i = 1; i <= cnt; ++i ) { VECTOR2I ipNext = ( i == cnt ? aPath.CPoint( 0 ) : aPath.CPoint( i ) ); if( ipNext.y == aP.y ) { if( ( ipNext.x == aP.x ) || ( ip.y == aP.y && ( ( ipNext.x > aP.x ) == ( ip.x < aP.x ) ) ) ) return true; } if( ( ip.y < aP.y ) != ( ipNext.y < aP.y ) ) { if( ip.x >= aP.x ) { if( ipNext.x > aP.x ) result = 1 - result; else { int64_t d = (int64_t)( ip.x - aP.x ) * (int64_t)( ipNext.y - aP.y ) - (int64_t)( ipNext.x - aP.x ) * (int64_t)( ip.y - aP.y ); if( !d ) return true; if( ( d > 0 ) == ( ipNext.y > ip.y ) ) result = 1 - result; } } else { if( ipNext.x > aP.x ) { int64_t d = (int64_t)( ip.x - aP.x ) * (int64_t)( ipNext.y - aP.y ) - (int64_t)( ipNext.x - aP.x ) * (int64_t)( ip.y - aP.y ); if( !d ) return true; if( ( d > 0 ) == ( ipNext.y > ip.y ) ) result = 1 - result; } } } ip = ipNext; } return result ? true : false; } void SHAPE_POLY_SET::Move( const VECTOR2I& aVector ) { for( POLYGON &poly : m_polys ) { for( SHAPE_LINE_CHAIN &path : poly ) { path.Move( aVector ); } } } int SHAPE_POLY_SET::TotalVertices() const { int c = 0; for( const POLYGON& poly : m_polys ) { for( const SHAPE_LINE_CHAIN& path : poly ) { c += path.PointCount(); } } return c; }