/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2015-2019 CERN * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors. * * @author Tomasz Wlostowski * @author Alejandro García Montoro * * 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 */ #ifndef __SHAPE_POLY_SET_H #define __SHAPE_POLY_SET_H #include #include // for deque #include // for vector #include // for string, stringstream #include #include // for set #include // for out_of_range #include // for abs #include #include // for ClipType, PolyTree (ptr only) #include // for SEG #include #include #include // for BOX2I #include // for VECTOR2I #include /** * Represent a set of closed polygons. Polygons may be nonconvex, self-intersecting * and have holes. Provides boolean operations (using Clipper library as the backend). * * Let us define the terms used on this class to clarify methods names and comments: * - Polygon: each polygon in the set. * - Outline: first polyline in each polygon; represents its outer contour. * - Hole: second and following polylines in the polygon. * - Contour: each polyline of each polygon in the set, whether or not it is an * outline or a hole. * - Vertex (or corner): each one of the points that define a contour. * * TODO: add convex partitioning & spatial index */ class SHAPE_POLY_SET : public SHAPE { public: ///< represents a single polygon outline with holes. The first entry is the outline, ///< the remaining (if any), are the holes ///< N.B. SWIG only supports typedef, so avoid c++ 'using' keyword typedef std::vector POLYGON; class TRIANGULATED_POLYGON { public: struct TRI : public SHAPE_LINE_CHAIN_BASE { TRI( int _a = 0, int _b = 0, int _c = 0, TRIANGULATED_POLYGON* aParent = nullptr ) : SHAPE_LINE_CHAIN_BASE( SH_POLY_SET_TRIANGLE ), a( _a ), b( _b ), c( _c ), parent( aParent ) { } virtual void Rotate( double aAngle, const VECTOR2I& aCenter = { 0, 0 } ) override {}; virtual void Move( const VECTOR2I& aVector ) override {}; virtual bool IsSolid() const override { return true; } virtual bool IsClosed() const override { return true; } virtual const BOX2I BBox( int aClearance = 0 ) const override; virtual const VECTOR2I GetPoint( int aIndex ) const override { switch(aIndex) { case 0: return parent->m_vertices[a]; case 1: return parent->m_vertices[b]; case 2: return parent->m_vertices[c]; default: assert(false); } return VECTOR2I(0, 0); } virtual const SEG GetSegment( int aIndex ) const override { switch(aIndex) { case 0: return SEG( parent->m_vertices[a], parent->m_vertices[b] ); case 1: return SEG( parent->m_vertices[b], parent->m_vertices[c] ); case 2: return SEG( parent->m_vertices[c], parent->m_vertices[a] ); default: assert(false); } return SEG(); } virtual size_t GetPointCount() const override { return 3; } virtual size_t GetSegmentCount() const override { return 3; } int a, b, c; TRIANGULATED_POLYGON* parent; }; TRIANGULATED_POLYGON(); TRIANGULATED_POLYGON( const TRIANGULATED_POLYGON& aOther ); ~TRIANGULATED_POLYGON(); void Clear() { m_vertices.clear(); m_triangles.clear(); } void GetTriangle( int index, VECTOR2I& a, VECTOR2I& b, VECTOR2I& c ) const { auto tri = m_triangles[ index ]; a = m_vertices[ tri.a ]; b = m_vertices[ tri.b ]; c = m_vertices[ tri.c ]; } TRIANGULATED_POLYGON& operator=( const TRIANGULATED_POLYGON& aOther ); void AddTriangle( int a, int b, int c ); void AddVertex( const VECTOR2I& aP ) { m_vertices.push_back( aP ); } size_t GetTriangleCount() const { return m_triangles.size(); } std::deque& Triangles() { return m_triangles; } size_t GetVertexCount() const { return m_vertices.size(); } void Move( const VECTOR2I& aVec ) { for( auto& vertex : m_vertices ) vertex += aVec; } private: std::deque m_triangles; std::deque m_vertices; }; /** * Structure to hold the necessary information in order to index a vertex on a * SHAPE_POLY_SET object: the polygon index, the contour index relative to the polygon and * the vertex index relative the contour. */ struct VERTEX_INDEX { int m_polygon; /*!< m_polygon is the index of the polygon. */ int m_contour; /*!< m_contour is the index of the contour relative to the polygon. */ int m_vertex; /*!< m_vertex is the index of the vertex relative to the contour. */ VERTEX_INDEX() : m_polygon(-1), m_contour(-1), m_vertex(-1) { } }; /** * Base class for iterating over all vertices in a given SHAPE_POLY_SET. */ template class ITERATOR_TEMPLATE { public: /** * @return true if the current vertex is the last one of the current contour * (outline or hole); false otherwise. */ bool IsEndContour() const { return m_currentVertex + 1 == m_poly->CPolygon( m_currentPolygon )[m_currentContour].PointCount(); } /** * @return true if the current outline is the last one; false otherwise. */ bool IsLastPolygon() const { return m_currentPolygon == m_lastPolygon; } operator bool() const { if( m_currentPolygon < m_lastPolygon ) return true; if( m_currentPolygon != m_poly->OutlineCount() - 1 ) return false; const auto& currentPolygon = m_poly->CPolygon( m_currentPolygon ); return m_currentContour < (int) currentPolygon.size() - 1 || m_currentVertex < currentPolygon[m_currentContour].PointCount(); } /** * Advance the indices of the current vertex/outline/contour, checking whether the * vertices in the holes have to be iterated through. */ void Advance() { // Advance vertex index m_currentVertex ++; // Check whether the user wants to iterate through the vertices of the holes // and behave accordingly if( m_iterateHoles ) { // If the last vertex of the contour was reached, advance the contour index if( m_currentVertex >= m_poly->CPolygon( m_currentPolygon )[m_currentContour].PointCount() ) { m_currentVertex = 0; m_currentContour++; // If the last contour of the current polygon was reached, advance the // outline index int totalContours = m_poly->CPolygon( m_currentPolygon ).size(); if( m_currentContour >= totalContours ) { m_currentContour = 0; m_currentPolygon++; } } } else { // If the last vertex of the outline was reached, advance to the following polygon if( m_currentVertex >= m_poly->CPolygon( m_currentPolygon )[0].PointCount() ) { m_currentVertex = 0; m_currentPolygon++; } } } void operator++( int dummy ) { Advance(); } void operator++() { Advance(); } const T& Get() { return m_poly->Polygon( m_currentPolygon )[m_currentContour].CPoint( m_currentVertex ); } const T& operator*() { return Get(); } const T* operator->() { return &Get(); } /** * @return the indices of the current polygon, contour and vertex. */ VERTEX_INDEX GetIndex() { VERTEX_INDEX index; index.m_polygon = m_currentPolygon; index.m_contour = m_currentContour; index.m_vertex = m_currentVertex; return index; } private: friend class SHAPE_POLY_SET; SHAPE_POLY_SET* m_poly; int m_currentPolygon; int m_currentContour; int m_currentVertex; int m_lastPolygon; bool m_iterateHoles; }; /** * Base class for iterating over all segments in a given SHAPE_POLY_SET. */ template class SEGMENT_ITERATOR_TEMPLATE { public: /** * @return true if the current outline is the last one. */ bool IsLastPolygon() const { return m_currentPolygon == m_lastPolygon; } operator bool() const { return m_currentPolygon <= m_lastPolygon; } /** * Advance the indices of the current vertex/outline/contour, checking whether the * vertices in the holes have to be iterated through. */ void Advance() { // Advance vertex index m_currentSegment++; int last; // Check whether the user wants to iterate through the vertices of the holes // and behave accordingly. if( m_iterateHoles ) { last = m_poly->CPolygon( m_currentPolygon )[m_currentContour].SegmentCount(); // If the last vertex of the contour was reached, advance the contour index. if( m_currentSegment >= last ) { m_currentSegment = 0; m_currentContour++; // If the last contour of the current polygon was reached, advance the // outline index. int totalContours = m_poly->CPolygon( m_currentPolygon ).size(); if( m_currentContour >= totalContours ) { m_currentContour = 0; m_currentPolygon++; } } } else { last = m_poly->CPolygon( m_currentPolygon )[0].SegmentCount(); // If the last vertex of the outline was reached, advance to the following // polygon if( m_currentSegment >= last ) { m_currentSegment = 0; m_currentPolygon++; } } } void operator++( int dummy ) { Advance(); } void operator++() { Advance(); } T Get() { return m_poly->Polygon( m_currentPolygon )[m_currentContour].Segment( m_currentSegment ); } T operator*() { return Get(); } /** * @return the indices of the current polygon, contour and vertex. */ VERTEX_INDEX GetIndex() const { VERTEX_INDEX index; index.m_polygon = m_currentPolygon; index.m_contour = m_currentContour; index.m_vertex = m_currentSegment; return index; } /** * @param aOther is an iterator pointing to another segment. * @return true if both iterators point to the same segment of the same contour of * the same polygon of the same polygon set; false otherwise. */ bool IsAdjacent( SEGMENT_ITERATOR_TEMPLATE aOther ) const { // Check that both iterators point to the same contour of the same polygon of the // same polygon set. if( m_poly == aOther.m_poly && m_currentPolygon == aOther.m_currentPolygon && m_currentContour == aOther.m_currentContour ) { // Compute the total number of segments. int numSeg; numSeg = m_poly->CPolygon( m_currentPolygon )[m_currentContour].SegmentCount(); // Compute the difference of the segment indices. If it is exactly one, they // are adjacent. The only missing case where they also are adjacent is when // the segments are the first and last one, in which case the difference // always equals the total number of segments minus one. int indexDiff = std::abs( m_currentSegment - aOther.m_currentSegment ); return ( indexDiff == 1 ) || ( indexDiff == (numSeg - 1) ); } return false; } private: friend class SHAPE_POLY_SET; SHAPE_POLY_SET* m_poly; int m_currentPolygon; int m_currentContour; int m_currentSegment; int m_lastPolygon; bool m_iterateHoles; }; // Iterator and const iterator types to visit polygon's points. typedef ITERATOR_TEMPLATE ITERATOR; typedef ITERATOR_TEMPLATE CONST_ITERATOR; // Iterator and const iterator types to visit polygon's edges. typedef SEGMENT_ITERATOR_TEMPLATE SEGMENT_ITERATOR; typedef SEGMENT_ITERATOR_TEMPLATE CONST_SEGMENT_ITERATOR; SHAPE_POLY_SET(); /** * Construct a SHAPE_POLY_SET with the first outline given by aOutline. * * @param aOutline is a closed outline */ SHAPE_POLY_SET( const SHAPE_LINE_CHAIN& aOutline ); /** * Copy constructor SHAPE_POLY_SET * Performs a deep copy of \p aOther into \p this. * * @param aOther is the SHAPE_POLY_SET object that will be copied. */ SHAPE_POLY_SET( const SHAPE_POLY_SET& aOther ); ~SHAPE_POLY_SET(); SHAPE_POLY_SET& operator=( const SHAPE_POLY_SET& aOther ); void CacheTriangulation( bool aPartition = true ); bool IsTriangulationUpToDate() const; MD5_HASH GetHash() const; virtual bool HasIndexableSubshapes() const override; virtual size_t GetIndexableSubshapeCount() const override; virtual void GetIndexableSubshapes( std::vector& aSubshapes ) override; /** * Convert a global vertex index ---i.e., a number that globally identifies a vertex in a * concatenated list of all vertices in all contours--- and get the index of the vertex * relative to the contour relative to the polygon in which it is. * * @param aGlobalIdx is the global index of the corner whose structured index wants to * be found * @param aRelativeIndices is a pointer to the set of relative indices to store. * @return true if the global index is correct and the information in \a aRelativeIndices * is valid; false otherwise. */ bool GetRelativeIndices( int aGlobalIdx, VERTEX_INDEX* aRelativeIndices ) const; /** * Compute the global index of a vertex from the relative indices of polygon, contour and * vertex. * * @param aRelativeIndices is the set of relative indices. * @param aGlobalIdx [out] is the computed global index. * @return true if the relative indices are correct; false otherwise. The computed * global index is returned in the \p aGlobalIdx reference. */ bool GetGlobalIndex( VERTEX_INDEX aRelativeIndices, int& aGlobalIdx ) const; /// @copydoc SHAPE::Clone() SHAPE* Clone() const override; ///< Creates a new empty polygon in the set and returns its index int NewOutline(); ///< Creates a new hole in a given outline int NewHole( int aOutline = -1 ); ///< Adds a new outline to the set and returns its index int AddOutline( const SHAPE_LINE_CHAIN& aOutline ); ///< Adds a new hole to the given outline (default: last) and returns its index int AddHole( const SHAPE_LINE_CHAIN& aHole, int aOutline = -1 ); ///< Return the area of this poly set double Area(); ///< Count the number of arc shapes present int ArcCount() const; ///< Appends all the arcs in this polyset to \a aArcBuffer void GetArcs( std::vector& aArcBuffer ) const; ///< Removes all arc references from all the outlines and holes in the polyset void ClearArcs(); ///< Appends a vertex at the end of the given outline/hole (default: the last outline) /** * Add a new vertex to the contour indexed by \p aOutline and \p aHole (defaults to the * outline of the last polygon). * * @param x is the x coordinate of the new vertex. * @param y is the y coordinate of the new vertex. * @param aOutline is the index of the polygon. * @param aHole is the index of the hole (-1 for the main outline), * @param aAllowDuplication is a flag to indicate whether it is allowed to add this * corner even if it is duplicated. * @return the number of corners of the selected contour after the addition. */ int Append( int x, int y, int aOutline = -1, int aHole = -1, bool aAllowDuplication = false ); ///< Merge polygons from two sets. void Append( const SHAPE_POLY_SET& aSet ); ///< Append a vertex at the end of the given outline/hole (default: the last outline) void Append( const VECTOR2I& aP, int aOutline = -1, int aHole = -1 ); /** * Append a new arc to the contour indexed by \p aOutline and \p aHole (defaults to the * outline of the last polygon). * @param aArc The arc to be inserted * @param aOutline Index of the polygon * @param aHole Index of the hole (-1 for the main outline) * @return the number of points in the arc (including the interpolated points from the arc) */ int Append( SHAPE_ARC& aArc, int aOutline = -1, int aHole = -1 ); /** * Adds a vertex in the globally indexed position \a aGlobalIndex. * * @param aGlobalIndex is the global index of the position in which the new vertex will be * inserted. * @param aNewVertex is the new inserted vertex. */ void InsertVertex( int aGlobalIndex, VECTOR2I aNewVertex ); ///< Return the index-th vertex in a given hole outline within a given outline const VECTOR2I& CVertex( int aIndex, int aOutline, int aHole ) const; ///< Return the aGlobalIndex-th vertex in the poly set const VECTOR2I& CVertex( int aGlobalIndex ) const; ///< Return the index-th vertex in a given hole outline within a given outline const VECTOR2I& CVertex( VERTEX_INDEX aIndex ) const; /** * Return the global indexes of the previous and the next corner of the \a aGlobalIndex-th * corner of a contour in the polygon set. * * They are often aGlobalIndex-1 and aGlobalIndex+1, but not for the first and last * corner of the contour. * * @param aGlobalIndex is index of the corner, globally indexed between all edges in all * contours * @param aPrevious is the globalIndex of the previous corner of the same contour. * @param aNext is the globalIndex of the next corner of the same contour. * @return true if OK, false if aGlobalIndex is out of range */ bool GetNeighbourIndexes( int aGlobalIndex, int* aPrevious, int* aNext ); /** * Check whether the aPolygonIndex-th polygon in the set is self intersecting. * * @param aPolygonIndex is the index of the polygon that wants to be checked. * @return true if the \a aPolygonIndex-th polygon is self intersecting, false otherwise. */ bool IsPolygonSelfIntersecting( int aPolygonIndex ) const; /** * Check whether any of the polygons in the set is self intersecting. * * @return true if any of the polygons is self intersecting, false otherwise. */ bool IsSelfIntersecting() const; ///< Return the number of triangulated polygons unsigned int TriangulatedPolyCount() const { return m_triangulatedPolys.size(); } ///< Return the number of outlines in the set int OutlineCount() const { return m_polys.size(); } ///< Return the number of vertices in a given outline/hole int VertexCount( int aOutline = -1, int aHole = -1 ) const; ///< Returns the number of holes in a given outline int HoleCount( int aOutline ) const { if( ( aOutline < 0 ) || ( aOutline >= (int) m_polys.size() ) || ( m_polys[aOutline].size() < 2 ) ) return 0; // the first polygon in m_polys[aOutline] is the main contour, // only others are holes: return m_polys[aOutline].size() - 1; } ///< Return the reference to aIndex-th outline in the set SHAPE_LINE_CHAIN& Outline( int aIndex ) { return m_polys[aIndex][0]; } const SHAPE_LINE_CHAIN& Outline( int aIndex ) const { return m_polys[aIndex][0]; } /** * Return a subset of the polygons in this set, the ones between \a aFirstPolygon and * \a aLastPolygon. * * @param aFirstPolygon is the first polygon to be included in the returned set. * @param aLastPolygon is the last polygon to be excluded of the returned set. * @return a set containing the polygons between \a aFirstPolygon (included) * and \a aLastPolygon (excluded). */ SHAPE_POLY_SET Subset( int aFirstPolygon, int aLastPolygon ); SHAPE_POLY_SET UnitSet( int aPolygonIndex ) { return Subset( aPolygonIndex, aPolygonIndex + 1 ); } ///< Return the reference to aHole-th hole in the aIndex-th outline SHAPE_LINE_CHAIN& Hole( int aOutline, int aHole ) { return m_polys[aOutline][aHole + 1]; } ///< Return the aIndex-th subpolygon in the set POLYGON& Polygon( int aIndex ) { return m_polys[aIndex]; } const POLYGON& Polygon( int aIndex ) const { return m_polys[aIndex]; } const TRIANGULATED_POLYGON* TriangulatedPolygon( int aIndex ) const { return m_triangulatedPolys[aIndex].get(); } const SHAPE_LINE_CHAIN& COutline( int aIndex ) const { return m_polys[aIndex][0]; } const SHAPE_LINE_CHAIN& CHole( int aOutline, int aHole ) const { return m_polys[aOutline][aHole + 1]; } const POLYGON& CPolygon( int aIndex ) const { return m_polys[aIndex]; } /** * Return an object to iterate through the points of the polygons between \p aFirst and * \p aLast. * * @param aFirst is the first polygon whose points will be iterated. * @param aLast is the last polygon whose points will be iterated. * @param aIterateHoles is a flag to indicate whether the points of the holes should be * iterated. * @return ITERATOR - the iterator object. */ ITERATOR Iterate( int aFirst, int aLast, bool aIterateHoles = false ) { ITERATOR iter; iter.m_poly = this; iter.m_currentPolygon = aFirst; iter.m_lastPolygon = aLast < 0 ? OutlineCount() - 1 : aLast; iter.m_currentContour = 0; iter.m_currentVertex = 0; iter.m_iterateHoles = aIterateHoles; return iter; } /** * @param aOutline is the index of the polygon to be iterated. * @return an iterator object to visit all points in the main outline of the * \a aOutline-th polygon, without visiting the points in the holes. */ ITERATOR Iterate( int aOutline ) { return Iterate( aOutline, aOutline ); } /** * @param aOutline the index of the polygon to be iterated. * @return an iterator object to visit all points in the main outline of the * \a aOutline-th polygon, visiting also the points in the holes. */ ITERATOR IterateWithHoles( int aOutline ) { return Iterate( aOutline, aOutline, true ); } /** * @return an iterator object to visit all points in all outlines of the set, * without visiting the points in the holes. */ ITERATOR Iterate() { return Iterate( 0, OutlineCount() - 1 ); } /** * @return an iterator object to visit all points in all outlines of the set, * visiting also the points in the holes. */ ITERATOR IterateWithHoles() { return Iterate( 0, OutlineCount() - 1, true ); } CONST_ITERATOR CIterate( int aFirst, int aLast, bool aIterateHoles = false ) const { CONST_ITERATOR iter; iter.m_poly = const_cast( this ); iter.m_currentPolygon = aFirst; iter.m_lastPolygon = aLast < 0 ? OutlineCount() - 1 : aLast; iter.m_currentContour = 0; iter.m_currentVertex = 0; iter.m_iterateHoles = aIterateHoles; return iter; } CONST_ITERATOR CIterate( int aOutline ) const { return CIterate( aOutline, aOutline ); } CONST_ITERATOR CIterateWithHoles( int aOutline ) const { return CIterate( aOutline, aOutline, true ); } CONST_ITERATOR CIterate() const { return CIterate( 0, OutlineCount() - 1 ); } CONST_ITERATOR CIterateWithHoles() const { return CIterate( 0, OutlineCount() - 1, true ); } ITERATOR IterateFromVertexWithHoles( int aGlobalIdx ) { // Build iterator ITERATOR iter = IterateWithHoles(); // Get the relative indices of the globally indexed vertex VERTEX_INDEX indices; if( !GetRelativeIndices( aGlobalIdx, &indices ) ) throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) ); // Adjust where the iterator is pointing iter.m_currentPolygon = indices.m_polygon; iter.m_currentContour = indices.m_contour; iter.m_currentVertex = indices.m_vertex; return iter; } ///< Return an iterator object, for iterating between aFirst and aLast outline, with or ///< without holes (default: without) SEGMENT_ITERATOR IterateSegments( int aFirst, int aLast, bool aIterateHoles = false ) { SEGMENT_ITERATOR iter; iter.m_poly = this; iter.m_currentPolygon = aFirst; iter.m_lastPolygon = aLast < 0 ? OutlineCount() - 1 : aLast; iter.m_currentContour = 0; iter.m_currentSegment = 0; iter.m_iterateHoles = aIterateHoles; return iter; } ///< Return an iterator object, for iterating between aFirst and aLast outline, with or ///< without holes (default: without) CONST_SEGMENT_ITERATOR CIterateSegments( int aFirst, int aLast, bool aIterateHoles = false ) const { CONST_SEGMENT_ITERATOR iter; iter.m_poly = const_cast( this ); iter.m_currentPolygon = aFirst; iter.m_lastPolygon = aLast < 0 ? OutlineCount() - 1 : aLast; iter.m_currentContour = 0; iter.m_currentSegment = 0; iter.m_iterateHoles = aIterateHoles; return iter; } ///< Return an iterator object, for iterating aPolygonIdx-th polygon edges. SEGMENT_ITERATOR IterateSegments( int aPolygonIdx ) { return IterateSegments( aPolygonIdx, aPolygonIdx ); } ///< Return an iterator object, for iterating aPolygonIdx-th polygon edges. CONST_SEGMENT_ITERATOR CIterateSegments( int aPolygonIdx ) const { return CIterateSegments( aPolygonIdx, aPolygonIdx ); } ///< Return an iterator object, for all outlines in the set (no holes). SEGMENT_ITERATOR IterateSegments() { return IterateSegments( 0, OutlineCount() - 1 ); } ///< Returns an iterator object, for all outlines in the set (no holes) CONST_SEGMENT_ITERATOR CIterateSegments() const { return CIterateSegments( 0, OutlineCount() - 1 ); } ///< Returns an iterator object, for all outlines in the set (with holes) SEGMENT_ITERATOR IterateSegmentsWithHoles() { return IterateSegments( 0, OutlineCount() - 1, true ); } ///< Return an iterator object, for the \a aOutline-th outline in the set (with holes). SEGMENT_ITERATOR IterateSegmentsWithHoles( int aOutline ) { return IterateSegments( aOutline, aOutline, true ); } ///< Return an iterator object, for the \a aOutline-th outline in the set (with holes). CONST_SEGMENT_ITERATOR CIterateSegmentsWithHoles() const { return CIterateSegments( 0, OutlineCount() - 1, true ); } ///< Return an iterator object, for the \a aOutline-th outline in the set (with holes). CONST_SEGMENT_ITERATOR CIterateSegmentsWithHoles( int aOutline ) const { return CIterateSegments( aOutline, aOutline, true ); } /** * Operations on polygons use a \a aFastMode param * if aFastMode is #PM_FAST (true) the result can be a weak polygon * if aFastMode is #PM_STRICTLY_SIMPLE (false) (default) the result is (theoretically) a * strictly simple polygon, but calculations can be really significantly time consuming * Most of time #PM_FAST is preferable. * #PM_STRICTLY_SIMPLE can be used in critical cases (Gerber output for instance) */ enum POLYGON_MODE { PM_FAST = true, PM_STRICTLY_SIMPLE = false }; ///< Perform boolean polyset union ///< For \a aFastMode meaning, see function booleanOp void BooleanAdd( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ); ///< Perform boolean polyset difference ///< For \a aFastMode meaning, see function booleanOp void BooleanSubtract( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ); ///< Perform boolean polyset intersection ///< For \a aFastMode meaning, see function booleanOp void BooleanIntersection( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ); ///< Perform boolean polyset union between a and b, store the result in it self ///< For \a aFastMode meaning, see function booleanOp void BooleanAdd( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ); ///< Perform boolean polyset difference between a and b, store the result in it self ///< For \a aFastMode meaning, see function booleanOp void BooleanSubtract( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ); ///< Perform boolean polyset intersection between a and b, store the result in it self ///< For \a aFastMode meaning, see function booleanOp void BooleanIntersection( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode ); enum CORNER_STRATEGY ///< define how inflate transform build inflated polygon { ALLOW_ACUTE_CORNERS, ///< just inflate the polygon. Acute angles create spikes CHAMFER_ACUTE_CORNERS, ///< Acute angles are chamfered ROUND_ACUTE_CORNERS, ///< Acute angles are rounded CHAMFER_ALL_CORNERS, ///< All angles are chamfered. ///< The distance between new and old polygon edges is not ///< constant, but do not change a lot ROUND_ALL_CORNERS ///< All angles are rounded. ///< The distance between new and old polygon edges is constant }; /** * Perform outline inflation/deflation. * * Polygons can have holes, but not linked holes with main outlines, if aFactor < 0. For * those use InflateWithLinkedHoles() to avoid odd corners where the link segments meet * the outline. * * @param aAmount is the number of units to offset edges. * @param aCircleSegCount is the number of segments per 360 degrees to use in curve approx * @param aCornerStrategy #ALLOW_ACUTE_CORNERS to preserve all angles, * #CHAMFER_ACUTE_CORNERS to chop angles less than 90°, * #ROUND_ACUTE_CORNERS to round off angles less than 90°, * #ROUND_ALL_CORNERS to round regardless of angles */ void Inflate( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy = ROUND_ALL_CORNERS ); void Deflate( int aAmount, int aCircleSegmentsCount, CORNER_STRATEGY aCornerStrategy = ROUND_ALL_CORNERS ) { Inflate( -aAmount, aCircleSegmentsCount, aCornerStrategy ); } /** * Perform outline inflation/deflation, using round corners. * * Polygons can have holes and/or linked holes with main outlines. The resulting * polygons are also polygons with linked holes to main outlines. For \a aFastMode * meaning, see function booleanOp . */ void InflateWithLinkedHoles( int aFactor, int aCircleSegmentsCount, POLYGON_MODE aFastMode ); ///< Convert a set of polygons with holes to a single outline with "slits"/"fractures" ///< connecting the outer ring to the inner holes ///< For \a aFastMode meaning, see function booleanOp void Fracture( POLYGON_MODE aFastMode ); ///< Convert a single outline slitted ("fractured") polygon into a set ouf outlines ///< with holes. void Unfracture( POLYGON_MODE aFastMode ); ///< Return true if the polygon set has any holes. bool HasHoles() const; ///< Return true if the polygon set has any holes that share a vertex. bool HasTouchingHoles() const; ///< Simplify the polyset (merges overlapping polys, eliminates degeneracy/self-intersections) ///< For \a aFastMode meaning, see function booleanOp void Simplify( POLYGON_MODE aFastMode ); /** * Convert a self-intersecting polygon to one (or more) non self-intersecting polygon(s). * * Removes null segments. * * @return the polygon count (always >= 1, because there is at least one polygon) * There are new polygons only if the polygon count is > 1. */ int NormalizeAreaOutlines(); /// @copydoc SHAPE::Format() const std::string Format() const override; /// @copydoc SHAPE::Parse() bool Parse( std::stringstream& aStream ) override; /// @copydoc SHAPE::Move() void Move( const VECTOR2I& aVector ) override; /** * Mirror the line points about y or x (or both) * * @param aX If true, mirror about the y axis (flip x coordinate) * @param aY If true, mirror about the x axis * @param aRef sets the reference point about which to mirror */ void Mirror( bool aX = true, bool aY = false, const VECTOR2I& aRef = { 0, 0 } ); /** * Rotate all vertices by a given angle. * * @param aCenter is the rotation center. * @param aAngle is the rotation angle in radians. */ void Rotate( double aAngle, const VECTOR2I& aCenter = { 0, 0 } ) override; /// @copydoc SHAPE::IsSolid() bool IsSolid() const override { return true; } const BOX2I BBox( int aClearance = 0 ) const override; /** * Check if point \a aP lies on an edge or vertex of some of the outlines or holes. * * @param aP is the point to check. * @return true if the point lies on the edge of any polygon. */ bool PointOnEdge( const VECTOR2I& aP ) const; /** * Check if the boundary of shape (this) lies closer to the shape \a aShape than \a aClearance, * indicating a collision. * * @param aShape shape to check collision against * @param aClearance minimum clearance * @param aActual [out] an optional pointer to an int to store the actual distance in the * event of a collision. * @param aLocation [out] an option pointer to a point to store a nearby location in the * event of a collision. * @return true if there is a collision. */ bool Collide( const SHAPE* aShape, int aClearance = 0, int* aActual = nullptr, VECTOR2I* aLocation = nullptr ) const override; /** * Check whether the point \a aP is either inside or on the edge of the polygon set. * * Note that prior to Jul 2020 we considered the edge to *not* be part of the polygon. * However, most other shapes (rects, circles, segments, etc.) include their edges and * the difference was causing issues when used for DRC. * * (FWIW, SHAPE_LINE_CHAIN was a split personality, with Collide() including its edges * but PointInside() not. That has also been corrected.) * * @param aP is the VECTOR2I point whose collision with respect to the poly set * will be tested. * @param aClearance is the security distance; if the point lies closer to the polygon * than aClearance distance, then there is a collision. * @param aActual an optional pointer to an int to store the actual distance in the event * of a collision. * @return true if the point aP collides with the polygon; false in any other case. */ bool Collide( const VECTOR2I& aP, int aClearance = 0, int* aActual = nullptr, VECTOR2I* aLocation = nullptr ) const override; /** * Check whether the segment \a aSeg collides with the polygon set (or its edge). * * Note that prior to Jul 2020 we considered the edge to *not* be part of the polygon. * However, most other shapes (rects, circles, segments, etc.) include their edges and * the difference was causing issues when used for DRC. * * (FWIW, SHAPE_LINE_CHAIN was a split personality, with Collide() including its edges * but PointInside() not. That has also been corrected.) * * @param aSeg is the SEG segment whose collision with respect to the poly set * will be tested. * @param aClearance is the security distance; if the segment passes closer to the polygon * than aClearance distance, then there is a collision. * @param aActual an optional pointer to an int to store the actual distance in the event * of a collision. * @return true if the segment aSeg collides with the polygon, false in any other case. */ bool Collide( const SEG& aSeg, int aClearance = 0, int* aActual = nullptr, VECTOR2I* aLocation = nullptr ) const override; /** * Check whether \a aPoint collides with any vertex of any of the contours of the polygon. * * @param aPoint is the #VECTOR2I point whose collision with respect to the polygon * will be tested. * @param aClearance is the security distance; if \p aPoint lies closer to a vertex than * aClearance distance, then there is a collision. * @param aClosestVertex is the index of the closes vertex to \p aPoint. * @return bool - true if there is a collision, false in any other case. */ bool CollideVertex( const VECTOR2I& aPoint, VERTEX_INDEX& aClosestVertex, int aClearance = 0 ) const; /** * Check whether aPoint collides with any edge of any of the contours of the polygon. * * @param aPoint is the VECTOR2I point whose collision with respect to the polygon * will be tested. * @param aClearance is the security distance; if \p aPoint lies closer to a vertex than * aClearance distance, then there is a collision. * @param aClosestVertex is the index of the closes vertex to \p aPoint. * @return bool - true if there is a collision, false in any other case. */ bool CollideEdge( const VECTOR2I& aPoint, VERTEX_INDEX& aClosestVertex, int aClearance = 0 ) const; /** * Construct BBoxCaches for Contains(), below. * * @note These caches **must** be built before a group of calls to Contains(). They are * **not** kept up-to-date by editing actions. */ void BuildBBoxCaches() const; const BOX2I BBoxFromCaches() const; /** * Return true if a given subpolygon contains the point \a aP. * * @param aP is the point to check * @param aSubpolyIndex is the subpolygon to check, or -1 to check all * @param aUseBBoxCaches gives faster performance when multiple calls are made with no * editing in between, but the caller MUST cache the bbox caches * before calling (via BuildBBoxCaches(), above) * @return true if the polygon contains the point */ bool Contains( const VECTOR2I& aP, int aSubpolyIndex = -1, int aAccuracy = 0, bool aUseBBoxCaches = false ) const; ///< Return true if the set is empty (no polygons at all) bool IsEmpty() const { return m_polys.empty(); } /** * Delete the \a aGlobalIndex-th vertex. * * @param aGlobalIndex is the global index of the to-be-removed vertex. */ void RemoveVertex( int aGlobalIndex ); /** * Delete the vertex indexed by \a aRelativeIndex (index of polygon, contour and vertex). * * @param aRelativeIndices is the set of relative indices of the to-be-removed vertex. */ void RemoveVertex( VERTEX_INDEX aRelativeIndices ); ///< Remove all outlines & holes (clears) the polygon set. void RemoveAllContours(); /** * Delete the \a aContourIdx-th contour of the \a aPolygonIdx-th polygon in the set. * * @param aContourIdx is the index of the contour in the aPolygonIdx-th polygon to be * removed. * @param aPolygonIdx is the index of the polygon in which the to-be-removed contour is. * Defaults to the last polygon in the set. */ void RemoveContour( int aContourIdx, int aPolygonIdx = -1 ); /** * Look for null segments; ie, segments whose ends are exactly the same and deletes them. * * @return the number of deleted segments. */ int RemoveNullSegments(); /** * Accessor function to set the position of a specific point. * * @param aIndex #VERTEX_INDEX of the point to move. * @param aPos destination position of the specified point. */ void SetVertex( const VERTEX_INDEX& aIndex, const VECTOR2I& aPos ); /** * Set the vertex based on the global index. * * Throws if the index doesn't exist. * * @param aGlobalIndex global index of the to-be-moved vertex * @param aPos New position on the vertex */ void SetVertex( int aGlobalIndex, const VECTOR2I& aPos ); ///< Return total number of vertices stored in the set. int TotalVertices() const; ///< Delete \a aIdx-th polygon from the set. void DeletePolygon( int aIdx ); /** * Return a chamfered version of the \a aIndex-th polygon. * * @param aDistance is the chamfering distance. * @param aIndex is the index of the polygon to be chamfered. * @return A polygon containing the chamfered version of the \a aIndex-th polygon. */ POLYGON ChamferPolygon( unsigned int aDistance, int aIndex ); /** * Return a filleted version of the \a aIndex-th polygon. * * @param aRadius is the fillet radius. * @param aErrorMax is the maximum allowable deviation of the polygon from the circle * @param aIndex is the index of the polygon to be filleted * @return A polygon containing the filleted version of the \a aIndex-th polygon. */ POLYGON FilletPolygon( unsigned int aRadius, int aErrorMax, int aIndex ); /** * Return a chamfered version of the polygon set. * * @param aDistance is the chamfering distance. * @return A set containing the chamfered version of this set. */ SHAPE_POLY_SET Chamfer( int aDistance ); /** * Return a filleted version of the polygon set. * * @param aRadius is the fillet radius. * @param aErrorMax is the maximum allowable deviation of the polygon from the circle * @return A set containing the filleted version of this set. */ SHAPE_POLY_SET Fillet( int aRadius, int aErrorMax ); /** * Compute the minimum distance between the \a aIndex-th polygon and \a aPoint. * * @param aPoint is the point whose distance to the aIndex-th polygon has to be measured. * @param aIndex is the index of the polygon whose distance to aPoint has to be measured. * @param aNearest [out] an optional pointer to be filled in with the point on the * polyset which is closest to aPoint. * @return The minimum distance between \a aPoint and all the segments of the \a aIndex-th * polygon. If the point is contained in the polygon, the distance is zero. */ SEG::ecoord SquaredDistanceToPolygon( VECTOR2I aPoint, int aIndex, VECTOR2I* aNearest ) const; /** * Compute the minimum distance between the aIndex-th polygon and aSegment with a * possible width. * * @param aSegment is the segment whose distance to the aIndex-th polygon has to be * measured. * @param aIndex is the index of the polygon whose distance to aPoint has to be measured. * @param aNearest [out] an optional pointer to be filled in with the point on the * polyset which is closest to aSegment. * @return The minimum distance between \a aSegment and all the segments of the \a aIndex-th * polygon. If the point is contained in the polygon, the distance is zero. */ SEG::ecoord SquaredDistanceToPolygon( const SEG& aSegment, int aIndex, VECTOR2I* aNearest) const; /** * Compute the minimum distance squared between aPoint and all the polygons in the set. * Squared distances are used because they avoid the cost of doing square-roots. * * @param aPoint is the point whose distance to the set has to be measured. * @param aNearest [out] an optional pointer to be filled in with the point on the * polyset which is closest to aPoint. * @return The minimum distance squared between aPoint and all the polygons in the set. * If the point is contained in any of the polygons, the distance is zero. */ SEG::ecoord SquaredDistance( VECTOR2I aPoint, VECTOR2I* aNearest = nullptr ) const; /** * Compute the minimum distance squared between aSegment and all the polygons in the set. * Squared distances are used because they avoid the cost of doing square-roots. * * @param aSegment is the segment whose distance to the polygon set has to be measured. * @param aSegmentWidth is the width of the segment; defaults to zero. * @param aNearest [out] an optional pointer to be filled in with the point on the * polyset which is closest to aSegment. * @return The minimum distance squared between aSegment and all the polygons in the set. * If the point is contained in the polygon, the distance is zero. */ SEG::ecoord SquaredDistance( const SEG& aSegment, VECTOR2I* aNearest = nullptr ) const; /** * Check whether the \a aGlobalIndex-th vertex belongs to a hole. * * @param aGlobalIdx is the index of the vertex. * @return true if the globally indexed \a aGlobalIdx-th vertex belongs to a hole. */ bool IsVertexInHole( int aGlobalIdx ); private: void fractureSingle( POLYGON& paths ); void unfractureSingle ( POLYGON& path ); void importTree( ClipperLib::PolyTree* tree, const std::vector& aZValueBuffer, const std::vector& aArcBuffe ); /** * This is the engine to execute all polygon boolean transforms (AND, OR, ... and polygon * simplification (merging overlapping polygons). * * @param aType is the transform type ( see ClipperLib::ClipType ) * @param aOtherShape is the SHAPE_LINE_CHAIN to combine with me. * @param aFastMode is an option to choose if the result can be a weak polygon * or a strictly simple polygon. * if aFastMode is PM_FAST the result can be a weak polygon * if aFastMode is PM_STRICTLY_SIMPLE (default) the result is (theoretically) a strictly * simple polygon, but calculations can be really significantly time consuming */ void booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode ); void booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aShape, const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode ); /** * Check whether the point \a aP is inside the \a aSubpolyIndex-th polygon of the polyset. If * the points lies on an edge, the polygon is considered to contain it. * * @param aP is the #VECTOR2I point whose position with respect to the inside of * the aSubpolyIndex-th polygon will be tested. * @param aSubpolyIndex is an integer specifying which polygon in the set has to be * checked. * @param aAccuracy accuracy in internal units * @param aUseBBoxCaches gives faster performance when multiple calls are made with no * editing in between, but the caller MUST cache the bbox caches * before calling (via BuildBBoxCaches(), above) * @return true if \a aP is inside aSubpolyIndex-th polygon; false in any other case. */ bool containsSingle( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy, bool aUseBBoxCaches = false ) const; /** * Operation ChamferPolygon and FilletPolygon are computed under the private chamferFillet * method; this enum is defined to make the necessary distinction when calling this method * from the public ChamferPolygon and FilletPolygon methods. */ enum CORNER_MODE { CHAMFERED, FILLETED }; /** * Return the chamfered or filleted version of the \a aIndex-th polygon in the set, depending * on the \a aMode selected * @param aMode represent which action will be taken: CORNER_MODE::CHAMFERED will * return a chamfered version of the polygon, CORNER_MODE::FILLETED will * return a filleted version of the polygon. * @param aDistance is the chamfering distance if aMode = CHAMFERED; if aMode = FILLETED, * is the filleting radius. * @param aIndex is the index of the polygon that will be chamfered/filleted. * @param aErrorMax is the maximum allowable deviation of the polygon from the circle * if aMode = FILLETED. If aMode = CHAMFERED, it is unused. * @return the chamfered/filleted version of the polygon. */ POLYGON chamferFilletPolygon( CORNER_MODE aMode, unsigned int aDistance, int aIndex, int aErrorMax ); ///< Return true if the polygon set has any holes that touch share a vertex. bool hasTouchingHoles( const POLYGON& aPoly ) const; MD5_HASH checksum() const; private: typedef std::vector POLYSET; POLYSET m_polys; std::vector> m_triangulatedPolys; bool m_triangulationValid = false; MD5_HASH m_hash; }; #endif // __SHAPE_POLY_SET_H