summaryrefslogtreecommitdiff
path: root/common/geometry/shape_poly_set.cpp
diff options
context:
space:
mode:
authorsaurabhb172020-02-26 16:20:48 +0530
committerGitHub2020-02-26 16:20:48 +0530
commitb77f5d9d8097c38159c6f60917995d6af13bbe1c (patch)
tree1392c90227aeea231c1d86371131e04c40382918 /common/geometry/shape_poly_set.cpp
parentdadc4d490966a24efe15b5cc533ef8695986048a (diff)
parent003d02608917e7a69d1a98438837e94ccf68352a (diff)
downloadKiCad-eSim-b77f5d9d8097c38159c6f60917995d6af13bbe1c.tar.gz
KiCad-eSim-b77f5d9d8097c38159c6f60917995d6af13bbe1c.tar.bz2
KiCad-eSim-b77f5d9d8097c38159c6f60917995d6af13bbe1c.zip
Merge pull request #4 from FOSSEE/develop
merging dev into master
Diffstat (limited to 'common/geometry/shape_poly_set.cpp')
-rw-r--r--common/geometry/shape_poly_set.cpp805
1 files changed, 805 insertions, 0 deletions
diff --git a/common/geometry/shape_poly_set.cpp b/common/geometry/shape_poly_set.cpp
new file mode 100644
index 0000000..ce28926
--- /dev/null
+++ b/common/geometry/shape_poly_set.cpp
@@ -0,0 +1,805 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2015 CERN
+ * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
+ *
+ * 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 <vector>
+#include <cstdio>
+#include <set>
+#include <list>
+#include <algorithm>
+
+#include <boost/foreach.hpp>
+
+#include <geometry/shape.h>
+#include <geometry/shape_line_chain.h>
+#include <geometry/shape_poly_set.h>
+
+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,
+ bool aFastMode )
+{
+ Clipper c;
+
+ if( !aFastMode )
+ c.StrictlySimple( true );
+
+ BOOST_FOREACH( 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 );
+ }
+
+ BOOST_FOREACH( 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,
+ bool aFastMode )
+{
+ Clipper c;
+
+ if( !aFastMode )
+ c.StrictlySimple( true );
+
+ BOOST_FOREACH( 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 );
+ }
+
+ BOOST_FOREACH( 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, bool aFastMode )
+{
+ booleanOp( ctUnion, b, aFastMode );
+}
+
+
+void SHAPE_POLY_SET::BooleanSubtract( const SHAPE_POLY_SET& b, bool aFastMode )
+{
+ booleanOp( ctDifference, b, aFastMode );
+}
+
+
+void SHAPE_POLY_SET::BooleanIntersection( const SHAPE_POLY_SET& b, bool aFastMode )
+{
+ booleanOp( ctIntersection, b, aFastMode );
+}
+
+
+void SHAPE_POLY_SET::BooleanAdd( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, bool aFastMode )
+{
+ booleanOp( ctUnion, a, b, aFastMode );
+}
+
+
+void SHAPE_POLY_SET::BooleanSubtract( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, bool aFastMode )
+{
+ booleanOp( ctDifference, a, b, aFastMode );
+}
+
+
+void SHAPE_POLY_SET::BooleanIntersection( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, bool aFastMode )
+{
+ booleanOp( ctIntersection, a, b, aFastMode );
+}
+
+
+void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount )
+{
+ ClipperOffset c;
+
+ BOOST_FOREACH( 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;
+
+ c.ArcTolerance = fabs( (double) aFactor ) / M_PI / aCircleSegmentsCount;
+
+ 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.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<FractureEdge*> 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<int>::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;
+
+ BOOST_FOREACH( SHAPE_LINE_CHAIN& path, paths )
+ {
+ int index = 0;
+
+ FractureEdge *prev = NULL, *first_edge = NULL;
+
+ int x_min = std::numeric_limits<int>::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<int>::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( bool aFastMode )
+{
+ Simplify( aFastMode ); // remove overlapping holes/degeneracy
+
+ BOOST_FOREACH( POLYGON& paths, m_polys )
+ {
+ fractureSingle( paths );
+ }
+}
+
+
+void SHAPE_POLY_SET::Simplify( bool 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] );
+
+ BOOST_FOREACH ( 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 )
+{
+ BOOST_FOREACH( POLYGON &poly, m_polys )
+ {
+ BOOST_FOREACH( SHAPE_LINE_CHAIN &path, poly )
+ {
+ path.Move( aVector );
+ }
+ }
+}
+
+
+int SHAPE_POLY_SET::TotalVertices() const
+{
+ int c = 0;
+
+ BOOST_FOREACH( const POLYGON& poly, m_polys )
+ {
+ BOOST_FOREACH ( const SHAPE_LINE_CHAIN& path, poly )
+ {
+ c += path.PointCount();
+ }
+ }
+
+ return c;
+}