kicad-source/common/geometry/shape_poly_set.cpp
Seth Hillbrand 490c805319 Allow squared inflation and inflate Eagle Zones
Sometimes we want to inflate a polygon without adding rounded edges.
This add the option using the jtMiter setting.

This is used in the Eagle parser to expand the Eagle zones for KiCad.
Eagle Zones are drawn on the polygon edge, so they extend out from the
outline.  KiCad zones are drawn inside the polygon.  We need to both
increase the zone size and decrease the minimum pen width to account for
this.

Fixes: lp:1817312
* https://bugs.launchpad.net/kicad/+bug/1817312
2019-05-21 13:49:18 -07:00

1974 lines
50 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015-2019 CERN
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* @author Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
*
* 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 <unordered_set>
#include <memory>
#include <md5_hash.h>
#include <map>
#include <make_unique.h>
#include <geometry/geometry_utils.h>
#include <geometry/shape.h>
#include <geometry/shape_line_chain.h>
#include <geometry/shape_poly_set.h>
#include <geometry/polygon_triangulation.h>
using namespace ClipperLib;
SHAPE_POLY_SET::SHAPE_POLY_SET() :
SHAPE( SH_POLY_SET )
{
}
SHAPE_POLY_SET::SHAPE_POLY_SET( const SHAPE_POLY_SET& aOther, bool aDeepCopy ) :
SHAPE( SH_POLY_SET ), m_polys( aOther.m_polys )
{
if( aOther.IsTriangulationUpToDate() )
{
for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
m_triangulatedPolys.push_back(
std::make_unique<TRIANGULATED_POLYGON>( *aOther.TriangulatedPolygon( i ) ) );
m_hash = aOther.GetHash();
m_triangulationValid = true;
}
}
SHAPE_POLY_SET::~SHAPE_POLY_SET()
{
}
SHAPE* SHAPE_POLY_SET::Clone() const
{
return new SHAPE_POLY_SET( *this );
}
bool SHAPE_POLY_SET::GetRelativeIndices( int aGlobalIdx,
SHAPE_POLY_SET::VERTEX_INDEX* aRelativeIndices ) const
{
int polygonIdx = 0;
unsigned int contourIdx = 0;
int vertexIdx = 0;
int currentGlobalIdx = 0;
for( polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
{
const POLYGON currentPolygon = CPolygon( polygonIdx );
for( contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
{
SHAPE_LINE_CHAIN currentContour = currentPolygon[contourIdx];
int totalPoints = currentContour.PointCount();
for( vertexIdx = 0; vertexIdx < totalPoints; vertexIdx++ )
{
// Check if the current vertex is the globally indexed as aGlobalIdx
if( currentGlobalIdx == aGlobalIdx )
{
aRelativeIndices->m_polygon = polygonIdx;
aRelativeIndices->m_contour = contourIdx;
aRelativeIndices->m_vertex = vertexIdx;
return true;
}
// Advance
currentGlobalIdx++;
}
}
}
return false;
}
bool SHAPE_POLY_SET::GetGlobalIndex( SHAPE_POLY_SET::VERTEX_INDEX aRelativeIndices,
int& aGlobalIdx )
{
int selectedVertex = aRelativeIndices.m_vertex;
unsigned int selectedContour = aRelativeIndices.m_contour;
unsigned int selectedPolygon = aRelativeIndices.m_polygon;
// Check whether the vertex indices make sense in this poly set
if( selectedPolygon < m_polys.size() && selectedContour < m_polys[selectedPolygon].size()
&& selectedVertex < m_polys[selectedPolygon][selectedContour].PointCount() )
{
POLYGON currentPolygon;
aGlobalIdx = 0;
for( unsigned int polygonIdx = 0; polygonIdx < selectedPolygon; polygonIdx++ )
{
currentPolygon = Polygon( polygonIdx );
for( unsigned int contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
{
aGlobalIdx += currentPolygon[contourIdx].PointCount();
}
}
currentPolygon = Polygon( selectedPolygon );
for( unsigned int contourIdx = 0; contourIdx < selectedContour; contourIdx++ )
{
aGlobalIdx += currentPolygon[contourIdx].PointCount();
}
aGlobalIdx += selectedVertex;
return true;
}
else
{
return false;
}
}
int SHAPE_POLY_SET::NewOutline()
{
SHAPE_LINE_CHAIN empty_path;
POLYGON poly;
empty_path.SetClosed( true );
poly.push_back( empty_path );
m_polys.push_back( poly );
return m_polys.size() - 1;
}
int SHAPE_POLY_SET::NewHole( int aOutline )
{
SHAPE_LINE_CHAIN empty_path;
empty_path.SetClosed( true );
// Default outline is the last one
if( aOutline < 0 )
aOutline += m_polys.size();
// Add hole to the selected outline
m_polys[aOutline].push_back( empty_path );
return m_polys.back().size() - 2;
}
int SHAPE_POLY_SET::Append( int x, int y, int aOutline, int aHole, bool aAllowDuplication )
{
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, aAllowDuplication );
return m_polys[aOutline][idx].PointCount();
}
void SHAPE_POLY_SET::InsertVertex( int aGlobalIndex, VECTOR2I aNewVertex )
{
VERTEX_INDEX index;
if( aGlobalIndex < 0 )
aGlobalIndex = 0;
if( aGlobalIndex >= TotalVertices() )
{
Append( aNewVertex );
}
else
{
// Assure the position to be inserted exists; throw an exception otherwise
if( GetRelativeIndices( aGlobalIndex, &index ) )
m_polys[index.m_polygon][index.m_contour].Insert( index.m_vertex, aNewVertex );
else
throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
}
}
int SHAPE_POLY_SET::VertexCount( int aOutline, int aHole ) const
{
if( m_polys.size() == 0 ) // Empty poly set
return 0;
if( aOutline < 0 ) // Use last outline
aOutline += m_polys.size();
int idx;
if( aHole < 0 )
idx = 0;
else
idx = aHole + 1;
if( aOutline >= (int) m_polys.size() ) // not existing outline
return 0;
if( idx >= (int) m_polys[aOutline].size() ) // not existing hole
return 0;
return m_polys[aOutline][idx].PointCount();
}
SHAPE_POLY_SET SHAPE_POLY_SET::Subset( int aFirstPolygon, int aLastPolygon )
{
assert( aFirstPolygon >= 0 && aLastPolygon <= OutlineCount() );
SHAPE_POLY_SET newPolySet;
for( int index = aFirstPolygon; index < aLastPolygon; index++ )
{
newPolySet.m_polys.push_back( Polygon( index ) );
}
return newPolySet;
}
VECTOR2I& SHAPE_POLY_SET::Vertex( int aIndex, 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( aIndex );
}
const VECTOR2I& SHAPE_POLY_SET::CVertex( int aIndex, 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( aIndex );
}
VECTOR2I& SHAPE_POLY_SET::Vertex( int aGlobalIndex )
{
SHAPE_POLY_SET::VERTEX_INDEX index;
// Assure the passed index references a legal position; abort otherwise
if( !GetRelativeIndices( aGlobalIndex, &index ) )
throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
return m_polys[index.m_polygon][index.m_contour].Point( index.m_vertex );
}
const VECTOR2I& SHAPE_POLY_SET::CVertex( int aGlobalIndex ) const
{
SHAPE_POLY_SET::VERTEX_INDEX index;
// Assure the passed index references a legal position; abort otherwise
if( !GetRelativeIndices( aGlobalIndex, &index ) )
throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
return m_polys[index.m_polygon][index.m_contour].CPoint( index.m_vertex );
}
VECTOR2I& SHAPE_POLY_SET::Vertex( SHAPE_POLY_SET::VERTEX_INDEX index )
{
return Vertex( index.m_vertex, index.m_polygon, index.m_contour - 1 );
}
const VECTOR2I& SHAPE_POLY_SET::CVertex( SHAPE_POLY_SET::VERTEX_INDEX index ) const
{
return CVertex( index.m_vertex, index.m_polygon, index.m_contour - 1 );
}
bool SHAPE_POLY_SET::GetNeighbourIndexes( int aGlobalIndex, int* aPrevious, int* aNext )
{
SHAPE_POLY_SET::VERTEX_INDEX index;
// If the edge does not exist, throw an exception, it is an illegal access memory error
if( !GetRelativeIndices( aGlobalIndex, &index ) )
return false;
// Calculate the previous and next index of aGlobalIndex, corresponding to
// the same contour;
VERTEX_INDEX inext = index;
int lastpoint = m_polys[index.m_polygon][index.m_contour].SegmentCount();
if( index.m_vertex == 0 )
{
index.m_vertex = lastpoint;
inext.m_vertex = 1;
}
else if( index.m_vertex == lastpoint )
{
index.m_vertex--;
inext.m_vertex = 0;
}
else
{
inext.m_vertex++;
index.m_vertex--;
}
if( aPrevious )
{
int previous;
GetGlobalIndex( index, previous );
*aPrevious = previous;
}
if( aNext )
{
int next;
GetGlobalIndex( inext, next );
*aNext = next;
}
return true;
}
bool SHAPE_POLY_SET::IsPolygonSelfIntersecting( int aPolygonIndex )
{
SEGMENT_ITERATOR iterator = IterateSegmentsWithHoles( aPolygonIndex );
SEGMENT_ITERATOR innerIterator;
for( iterator = IterateSegmentsWithHoles( aPolygonIndex ); iterator; iterator++ )
{
SEG firstSegment = *iterator;
// Iterate through all remaining segments.
innerIterator = iterator;
// Start in the next segment, we don't want to check collision between a segment and itself
for( innerIterator++; innerIterator; innerIterator++ )
{
SEG secondSegment = *innerIterator;
// Check whether the two segments built collide, only when they are not adjacent.
if( !iterator.IsAdjacent( innerIterator ) && firstSegment.Collide( secondSegment, 0 ) )
return true;
}
}
return false;
}
bool SHAPE_POLY_SET::IsSelfIntersecting()
{
for( unsigned int polygon = 0; polygon < m_polys.size(); polygon++ )
{
if( IsPolygonSelfIntersecting( polygon ) )
return true;
}
return false;
}
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;
}
void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aOtherShape,
POLYGON_MODE aFastMode )
{
booleanOp( aType, *this, aOtherShape, aFastMode );
}
void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType aType,
const SHAPE_POLY_SET& aShape,
const SHAPE_POLY_SET& aOtherShape,
POLYGON_MODE aFastMode )
{
Clipper c;
c.StrictlySimple( aFastMode == PM_STRICTLY_SIMPLE );
for( auto poly : aShape.m_polys )
{
for( size_t i = 0 ; i < poly.size(); i++ )
c.AddPath( poly[i].convertToClipper( i == 0 ), ptSubject, true );
}
for( auto poly : aOtherShape.m_polys )
{
for( size_t i = 0; i < poly.size(); i++ )
c.AddPath( poly[i].convertToClipper( i == 0 ), 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::InflateWithLinkedHoles( int aFactor, int aCircleSegmentsCount, POLYGON_MODE aFastMode )
{
Simplify( aFastMode );
Inflate( aFactor, aCircleSegmentsCount );
Fracture( aFastMode );
}
void SHAPE_POLY_SET::Inflate( int aFactor, int aCircleSegmentsCount, bool aPreseveCorners )
{
// 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;
// N.B. using jtSquare here does not create square corners. They end up mitered by
// aFactor. Setting jtMiter and forcing the limit to be aFactor creates sharp corners.
JoinType type = aPreseveCorners ? jtMiter : jtRound;
for( const POLYGON& poly : m_polys )
{
for( size_t i = 0; i < poly.size(); i++ )
c.AddPath( poly[i].convertToClipper( i == 0 ), type, 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.MiterLimit = std::abs( aFactor );
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( n->Contour );
for( unsigned int i = 0; i < n->Childs.size(); i++ )
paths.push_back( n->Childs[i]->Contour );
m_polys.push_back( paths );
}
}
}
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;
for( 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( POLYGON_MODE aFastMode )
{
Simplify( aFastMode ); // remove overlapping holes/degeneracy
for( POLYGON& paths : m_polys )
{
fractureSingle( paths );
}
}
void SHAPE_POLY_SET::unfractureSingle( SHAPE_POLY_SET::POLYGON& aPoly )
{
assert( aPoly.size() == 1 );
struct EDGE
{
int m_index = 0;
SHAPE_LINE_CHAIN* m_poly = nullptr;
bool m_duplicate = false;
EDGE( SHAPE_LINE_CHAIN* aPolygon, int aIndex ) :
m_index( aIndex ),
m_poly( aPolygon )
{}
bool compareSegs( const SEG& s1, const SEG& s2 ) const
{
return (s1.A == s2.B && s1.B == s2.A);
}
bool operator==( const EDGE& aOther ) const
{
return compareSegs( m_poly->CSegment( m_index ),
aOther.m_poly->CSegment( aOther.m_index ) );
}
bool operator!=( const EDGE& aOther ) const
{
return !compareSegs( m_poly->CSegment( m_index ),
aOther.m_poly->CSegment( aOther.m_index ) );
}
struct HASH
{
std::size_t operator()( const EDGE& aEdge ) const
{
const auto& a = aEdge.m_poly->CSegment( aEdge.m_index );
return (std::size_t) ( a.A.x + a.B.x + a.A.y + a.B.y );
}
};
};
struct EDGE_LIST_ENTRY
{
int index;
EDGE_LIST_ENTRY* next;
};
std::unordered_set<EDGE, EDGE::HASH> uniqueEdges;
auto lc = aPoly[0];
lc.Simplify();
auto edgeList = std::make_unique<EDGE_LIST_ENTRY []>( lc.SegmentCount() );
for( int i = 0; i < lc.SegmentCount(); i++ )
{
edgeList[i].index = i;
edgeList[i].next = &edgeList[ (i != lc.SegmentCount() - 1) ? i + 1 : 0 ];
}
std::unordered_set<EDGE_LIST_ENTRY*> queue;
for( int i = 0; i < lc.SegmentCount(); i++ )
{
EDGE e( &lc, i );
uniqueEdges.insert( e );
}
for( int i = 0; i < lc.SegmentCount(); i++ )
{
EDGE e( &lc, i );
auto it = uniqueEdges.find( e );
if( it != uniqueEdges.end() && it->m_index != i )
{
int e1 = it->m_index;
int e2 = i;
if( e1 > e2 )
std::swap( e1, e2 );
int e1_prev = e1 - 1;
if( e1_prev < 0 )
e1_prev = lc.SegmentCount() - 1;
int e2_prev = e2 - 1;
if( e2_prev < 0 )
e2_prev = lc.SegmentCount() - 1;
int e1_next = e1 + 1;
if( e1_next == lc.SegmentCount() )
e1_next = 0;
int e2_next = e2 + 1;
if( e2_next == lc.SegmentCount() )
e2_next = 0;
edgeList[e1_prev].next = &edgeList[ e2_next ];
edgeList[e2_prev].next = &edgeList[ e1_next ];
edgeList[i].next = nullptr;
edgeList[it->m_index].next = nullptr;
}
}
for( int i = 0; i < lc.SegmentCount(); i++ )
{
if( edgeList[i].next )
queue.insert( &edgeList[i] );
}
auto edgeBuf = std::make_unique<EDGE_LIST_ENTRY* []>( lc.SegmentCount() );
int n = 0;
int outline = -1;
POLYGON result;
while( queue.size() )
{
auto e_first = (*queue.begin() );
auto e = e_first;
int cnt = 0;
do {
edgeBuf[cnt++] = e;
e = e->next;
} while( e && e != e_first );
SHAPE_LINE_CHAIN outl;
for( int i = 0; i < cnt; i++ )
{
auto p = lc.CPoint( edgeBuf[i]->index );
outl.Append( p );
queue.erase( edgeBuf[i] );
}
outl.SetClosed( true );
bool cw = outl.Area() > 0.0;
if( cw )
outline = n;
result.push_back( outl );
n++;
}
if( outline > 0 )
std::swap( result[0], result[outline] );
aPoly = result;
}
bool SHAPE_POLY_SET::HasHoles() const
{
// Iterate through all the polygons on the set
for( const POLYGON& paths : m_polys )
{
// If any of them has more than one contour, it is a hole.
if( paths.size() > 1 )
return true;
}
// Return false if and only if every polygon has just one outline, without holes.
return false;
}
void SHAPE_POLY_SET::Unfracture( POLYGON_MODE aFastMode )
{
for( POLYGON& path : m_polys )
{
unfractureSingle( path );
}
Simplify( aFastMode ); // remove overlapping holes/degeneracy
}
void SHAPE_POLY_SET::Simplify( POLYGON_MODE aFastMode )
{
SHAPE_POLY_SET empty;
booleanOp( ctUnion, empty, aFastMode );
}
int SHAPE_POLY_SET::NormalizeAreaOutlines()
{
// We are expecting only one main outline, but this main outline can have holes
// if holes: combine holes and remove them from the main outline.
// Note also we are using SHAPE_POLY_SET::PM_STRICTLY_SIMPLE in polygon
// calculations, but it is not mandatory. It is used mainly
// because there is usually only very few vertices in area outlines
SHAPE_POLY_SET::POLYGON& outline = Polygon( 0 );
SHAPE_POLY_SET holesBuffer;
// Move holes stored in outline to holesBuffer:
// The first SHAPE_LINE_CHAIN is the main outline, others are holes
while( outline.size() > 1 )
{
holesBuffer.AddOutline( outline.back() );
outline.pop_back();
}
Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
// If any hole, substract it to main outline
if( holesBuffer.OutlineCount() )
{
holesBuffer.Simplify( SHAPE_POLY_SET::PM_FAST );
BooleanSubtract( holesBuffer, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
}
RemoveNullSegments();
return OutlineCount();
}
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;
}
bool SHAPE_POLY_SET::PointOnEdge( const VECTOR2I& aP ) const
{
// Iterate through all the polygons in the set
for( const POLYGON& polygon : m_polys )
{
// Iterate through all the line chains in the polygon
for( const SHAPE_LINE_CHAIN& lineChain : polygon )
{
if( lineChain.PointOnEdge( aP ) )
return true;
}
}
return false;
}
bool SHAPE_POLY_SET::Collide( const SEG& aSeg, int aClearance ) const
{
SHAPE_POLY_SET polySet = SHAPE_POLY_SET( *this );
// Inflate the polygon if necessary.
if( aClearance > 0 )
{
// fixme: the number of arc segments should not be hardcoded
polySet.Inflate( aClearance, 8 );
}
// We are going to check to see if the segment crosses an external
// boundary. However, if the full segment is inside the polyset, this
// will not be true. So we first test to see if one of the points is
// inside. If true, then we collide
if( polySet.Contains( aSeg.A ) )
return true;
for( SEGMENT_ITERATOR iterator = polySet.IterateSegmentsWithHoles(); iterator; iterator++ )
{
SEG polygonEdge = *iterator;
if( polygonEdge.Intersect( aSeg, true ) )
return true;
}
return false;
}
bool SHAPE_POLY_SET::Collide( const VECTOR2I& aP, int aClearance ) const
{
SHAPE_POLY_SET polySet = SHAPE_POLY_SET( *this );
// Inflate the polygon if necessary.
if( aClearance > 0 )
{
// fixme: the number of arc segments should not be hardcoded
polySet.Inflate( aClearance, 8 );
}
// There is a collision if and only if the point is inside of the polygon.
return polySet.Contains( aP );
}
void SHAPE_POLY_SET::RemoveAllContours()
{
m_polys.clear();
}
void SHAPE_POLY_SET::RemoveContour( int aContourIdx, int aPolygonIdx )
{
// Default polygon is the last one
if( aPolygonIdx < 0 )
aPolygonIdx += m_polys.size();
m_polys[aPolygonIdx].erase( m_polys[aPolygonIdx].begin() + aContourIdx );
}
int SHAPE_POLY_SET::RemoveNullSegments()
{
int removed = 0;
ITERATOR iterator = IterateWithHoles();
VECTOR2I contourStart = *iterator;
VECTOR2I segmentStart, segmentEnd;
VERTEX_INDEX indexStart;
while( iterator )
{
// Obtain first point and its index
segmentStart = *iterator;
indexStart = iterator.GetIndex();
// Obtain last point
if( iterator.IsEndContour() )
{
segmentEnd = contourStart;
// Advance
iterator++;
if( iterator )
contourStart = *iterator;
}
else
{
// Advance
iterator++;
if( iterator )
segmentEnd = *iterator;
}
// Remove segment start if both points are equal
if( segmentStart == segmentEnd )
{
RemoveVertex( indexStart );
removed++;
// Advance the iterator one position, as there is one vertex less.
if( iterator )
iterator++;
}
}
return removed;
}
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::CollideVertex( const VECTOR2I& aPoint,
SHAPE_POLY_SET::VERTEX_INDEX& aClosestVertex, int aClearance )
{
// Shows whether there was a collision
bool collision = false;
// Difference vector between each vertex and aPoint.
VECTOR2D delta;
double distance, clearance;
// Convert clearance to double for precission when comparing distances
clearance = aClearance;
for( ITERATOR iterator = IterateWithHoles(); iterator; iterator++ )
{
// Get the difference vector between current vertex and aPoint
delta = *iterator - aPoint;
// Compute distance
distance = delta.EuclideanNorm();
// Check for collisions
if( distance <= clearance )
{
collision = true;
// Update aClearance to look for closer vertices
clearance = distance;
// Store the indices that identify the vertex
aClosestVertex = iterator.GetIndex();
}
}
return collision;
}
bool SHAPE_POLY_SET::CollideEdge( const VECTOR2I& aPoint,
SHAPE_POLY_SET::VERTEX_INDEX& aClosestVertex, int aClearance )
{
// Shows whether there was a collision
bool collision = false;
SEGMENT_ITERATOR iterator;
for( iterator = IterateSegmentsWithHoles(); iterator; iterator++ )
{
SEG currentSegment = *iterator;
int distance = currentSegment.Distance( aPoint );
// Check for collisions
if( distance <= aClearance )
{
collision = true;
// Update aClearance to look for closer edges
aClearance = distance;
// Store the indices that identify the vertex
aClosestVertex = iterator.GetIndex();
}
}
return collision;
}
bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles ) const
{
if( m_polys.size() == 0 ) // empty set?
return false;
// If there is a polygon specified, check the condition against that polygon
if( aSubpolyIndex >= 0 )
return containsSingle( aP, aSubpolyIndex, aIgnoreHoles );
// In any other case, check it against all polygons in the set
for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
{
if( containsSingle( aP, polygonIdx, aIgnoreHoles ) )
return true;
}
return false;
}
void SHAPE_POLY_SET::RemoveVertex( int aGlobalIndex )
{
VERTEX_INDEX index;
// Assure the to be removed vertex exists, abort otherwise
if( GetRelativeIndices( aGlobalIndex, &index ) )
RemoveVertex( index );
else
throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
}
void SHAPE_POLY_SET::RemoveVertex( VERTEX_INDEX aIndex )
{
m_polys[aIndex.m_polygon][aIndex.m_contour].Remove( aIndex.m_vertex );
}
bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, bool aIgnoreHoles ) const
{
// Check that the point is inside the outline
if( pointInPolygon( aP, m_polys[aSubpolyIndex][0] ) )
{
if( !aIgnoreHoles )
{
// Check that the point is not in any of the holes
for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ )
{
const SHAPE_LINE_CHAIN hole = CHole( aSubpolyIndex, holeIdx );
// If the point is inside a hole (and not on its edge),
// it is outside of the polygon
if( pointInPolygon( aP, hole ) && !hole.PointOnEdge( aP ) )
return false;
}
}
return true;
}
return false;
}
bool SHAPE_POLY_SET::pointInPolygon( const VECTOR2I& aP, const SHAPE_LINE_CHAIN& aPath ) const
{
return aPath.PointInside( aP );
}
void SHAPE_POLY_SET::Move( const VECTOR2I& aVector )
{
for( POLYGON& poly : m_polys )
{
for( SHAPE_LINE_CHAIN& path : poly )
{
path.Move( aVector );
}
}
}
void SHAPE_POLY_SET::Rotate( double aAngle, const VECTOR2I& aCenter )
{
for( POLYGON& poly : m_polys )
{
for( SHAPE_LINE_CHAIN& path : poly )
{
path.Rotate( aAngle, aCenter );
}
}
}
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;
}
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex )
{
return chamferFilletPolygon( CORNER_MODE::CHAMFERED, aDistance, aIndex );
}
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius,
int aErrorMax,
int aIndex )
{
return chamferFilletPolygon( CORNER_MODE::FILLETED, aRadius, aIndex, aErrorMax );
}
int SHAPE_POLY_SET::DistanceToPolygon( VECTOR2I aPoint, int aPolygonIndex )
{
// We calculate the min dist between the segment and each outline segment
// However, if the segment to test is inside the outline, and does not cross
// any edge, it can be seen outside the polygon.
// Therefore test if a segment end is inside ( testing only one end is enough )
if( containsSingle( aPoint, aPolygonIndex ) )
return 0;
SEGMENT_ITERATOR iterator = IterateSegmentsWithHoles( aPolygonIndex );
SEG polygonEdge = *iterator;
int minDistance = polygonEdge.Distance( aPoint );
for( iterator++; iterator && minDistance > 0; iterator++ )
{
polygonEdge = *iterator;
int currentDistance = polygonEdge.Distance( aPoint );
if( currentDistance < minDistance )
minDistance = currentDistance;
}
return minDistance;
}
int SHAPE_POLY_SET::DistanceToPolygon( SEG aSegment, int aPolygonIndex, int aSegmentWidth )
{
// We calculate the min dist between the segment and each outline segment
// However, if the segment to test is inside the outline, and does not cross
// any edge, it can be seen outside the polygon.
// Therefore test if a segment end is inside ( testing only one end is enough )
if( containsSingle( aSegment.A, aPolygonIndex ) )
return 0;
SEGMENT_ITERATOR iterator = IterateSegmentsWithHoles( aPolygonIndex );
SEG polygonEdge = *iterator;
int minDistance = polygonEdge.Distance( aSegment );
for( iterator++; iterator && minDistance > 0; iterator++ )
{
polygonEdge = *iterator;
int currentDistance = polygonEdge.Distance( aSegment );
if( currentDistance < minDistance )
minDistance = currentDistance;
}
// Take into account the width of the segment
if( aSegmentWidth > 0 )
minDistance -= aSegmentWidth / 2;
// Return the maximum of minDistance and zero
return minDistance < 0 ? 0 : minDistance;
}
int SHAPE_POLY_SET::Distance( VECTOR2I aPoint )
{
int currentDistance;
int minDistance = DistanceToPolygon( aPoint, 0 );
// Iterate through all the polygons and get the minimum distance.
for( unsigned int polygonIdx = 1; polygonIdx < m_polys.size(); polygonIdx++ )
{
currentDistance = DistanceToPolygon( aPoint, polygonIdx );
if( currentDistance < minDistance )
minDistance = currentDistance;
}
return minDistance;
}
int SHAPE_POLY_SET::Distance( const SEG& aSegment, int aSegmentWidth )
{
int currentDistance;
int minDistance = DistanceToPolygon( aSegment, 0, aSegmentWidth );
// Iterate through all the polygons and get the minimum distance.
for( unsigned int polygonIdx = 1; polygonIdx < m_polys.size(); polygonIdx++ )
{
currentDistance = DistanceToPolygon( aSegment, polygonIdx, aSegmentWidth );
if( currentDistance < minDistance )
minDistance = currentDistance;
}
return minDistance;
}
bool SHAPE_POLY_SET::IsVertexInHole( int aGlobalIdx )
{
VERTEX_INDEX index;
// Get the polygon and contour where the vertex is. If the vertex does not exist, return false
if( !GetRelativeIndices( aGlobalIdx, &index ) )
return false;
// The contour is a hole if its index is greater than zero
return index.m_contour > 0;
}
SHAPE_POLY_SET SHAPE_POLY_SET::Chamfer( int aDistance )
{
SHAPE_POLY_SET chamfered;
for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
chamfered.m_polys.push_back( ChamferPolygon( aDistance, polygonIdx ) );
return chamfered;
}
SHAPE_POLY_SET SHAPE_POLY_SET::Fillet( int aRadius, int aErrorMax )
{
SHAPE_POLY_SET filleted;
for( size_t polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, polygonIdx ) );
return filleted;
}
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::chamferFilletPolygon( CORNER_MODE aMode,
unsigned int aDistance,
int aIndex,
int aErrorMax )
{
// Null segments create serious issues in calculations. Remove them:
RemoveNullSegments();
SHAPE_POLY_SET::POLYGON currentPoly = Polygon( aIndex );
SHAPE_POLY_SET::POLYGON newPoly;
// If the chamfering distance is zero, then the polygon remain intact.
if( aDistance == 0 )
{
return currentPoly;
}
// Iterate through all the contours (outline and holes) of the polygon.
for( SHAPE_LINE_CHAIN& currContour : currentPoly )
{
// Generate a new contour in the new polygon
SHAPE_LINE_CHAIN newContour;
// Iterate through the vertices of the contour
for( int currVertex = 0; currVertex < currContour.PointCount(); currVertex++ )
{
// Current vertex
int x1 = currContour.Point( currVertex ).x;
int y1 = currContour.Point( currVertex ).y;
// Indices for previous and next vertices.
int prevVertex;
int nextVertex;
// Previous and next vertices indices computation. Necessary to manage the edge cases.
// Previous vertex is the last one if the current vertex is the first one
prevVertex = currVertex == 0 ? currContour.PointCount() - 1 : currVertex - 1;
// next vertex is the first one if the current vertex is the last one.
nextVertex = currVertex == currContour.PointCount() - 1 ? 0 : currVertex + 1;
// Previous vertex computation
double xa = currContour.Point( prevVertex ).x - x1;
double ya = currContour.Point( prevVertex ).y - y1;
// Next vertex computation
double xb = currContour.Point( nextVertex ).x - x1;
double yb = currContour.Point( nextVertex ).y - y1;
// Compute the new distances
double lena = hypot( xa, ya );
double lenb = hypot( xb, yb );
// Make the final computations depending on the mode selected, chamfered or filleted.
if( aMode == CORNER_MODE::CHAMFERED )
{
double distance = aDistance;
// Chamfer one half of an edge at most
if( 0.5 * lena < distance )
distance = 0.5 * lena;
if( 0.5 * lenb < distance )
distance = 0.5 * lenb;
int nx1 = round_nearest( distance * xa / lena );
int ny1 = round_nearest( distance * ya / lena );
newContour.Append( x1 + nx1, y1 + ny1 );
int nx2 = round_nearest( distance * xb / lenb );
int ny2 = round_nearest( distance * yb / lenb );
newContour.Append( x1 + nx2, y1 + ny2 );
}
else // CORNER_MODE = FILLETED
{
double cosine = ( xa * xb + ya * yb ) / ( lena * lenb );
double radius = aDistance;
double denom = sqrt( 2.0 / ( 1 + cosine ) - 1 );
// Do nothing in case of parallel edges
if( std::isinf( denom ) )
continue;
// Limit rounding distance to one half of an edge
if( 0.5 * lena * denom < radius )
radius = 0.5 * lena * denom;
if( 0.5 * lenb * denom < radius )
radius = 0.5 * lenb * denom;
// Calculate fillet arc absolute center point (xc, yx)
double k = radius / sqrt( .5 * ( 1 - cosine ) );
double lenab = sqrt( ( xa / lena + xb / lenb ) * ( xa / lena + xb / lenb ) +
( ya / lena + yb / lenb ) * ( ya / lena + yb / lenb ) );
double xc = x1 + k * ( xa / lena + xb / lenb ) / lenab;
double yc = y1 + k * ( ya / lena + yb / lenb ) / lenab;
// Calculate arc start and end vectors
k = radius / sqrt( 2 / ( 1 + cosine ) - 1 );
double xs = x1 + k * xa / lena - xc;
double ys = y1 + k * ya / lena - yc;
double xe = x1 + k * xb / lenb - xc;
double ye = y1 + k * yb / lenb - yc;
// Cosine of arc angle
double argument = ( xs * xe + ys * ye ) / ( radius * radius );
// Make sure the argument is in [-1,1], interval in which the acos function is
// defined
if( argument < -1 )
argument = -1;
else if( argument > 1 )
argument = 1;
double arcAngle = acos( argument );
double arcAngleDegrees = arcAngle * 180.0 / M_PI;
int segments = GetArcToSegmentCount( radius, aErrorMax, arcAngleDegrees );
double deltaAngle = arcAngle / segments;
double startAngle = atan2( -ys, xs );
// Flip arc for inner corners
if( xa * yb - ya * xb <= 0 )
deltaAngle *= -1;
double nx = xc + xs;
double ny = yc + ys;
newContour.Append( round_nearest( nx ), round_nearest( ny ) );
// Store the previous added corner to make a sanity check
int prevX = round_nearest( nx );
int prevY = round_nearest( ny );
for( int j = 0; j < segments; j++ )
{
nx = xc + cos( startAngle + ( j + 1 ) * deltaAngle ) * radius;
ny = yc - sin( startAngle + ( j + 1 ) * deltaAngle ) * radius;
// Sanity check: the rounding can produce repeated corners; do not add them.
if( round_nearest( nx ) != prevX || round_nearest( ny ) != prevY )
{
newContour.Append( round_nearest( nx ), round_nearest( ny ) );
prevX = round_nearest( nx );
prevY = round_nearest( ny );
}
}
}
}
// Close the current contour and add it the new polygon
newContour.SetClosed( true );
newPoly.push_back( newContour );
}
return newPoly;
}
SHAPE_POLY_SET &SHAPE_POLY_SET::operator=( const SHAPE_POLY_SET& aOther )
{
static_cast<SHAPE&>(*this) = aOther;
m_polys = aOther.m_polys;
// reset poly cache:
m_hash = MD5_HASH{};
m_triangulationValid = false;
m_triangulatedPolys.clear();
return *this;
}
MD5_HASH SHAPE_POLY_SET::GetHash() const
{
if( !m_hash.IsValid() )
return checksum();
return m_hash;
}
bool SHAPE_POLY_SET::IsTriangulationUpToDate() const
{
if( !m_triangulationValid )
return false;
if( !m_hash.IsValid() )
return false;
auto hash = checksum();
return hash == m_hash;
}
void SHAPE_POLY_SET::CacheTriangulation()
{
bool recalculate = !m_hash.IsValid();
MD5_HASH hash;
if( !m_triangulationValid )
recalculate = true;
if( !recalculate )
{
hash = checksum();
if( m_hash != hash )
{
m_hash = hash;
recalculate = true;
}
}
if( !recalculate )
return;
SHAPE_POLY_SET tmpSet = *this;
if( tmpSet.HasHoles() )
tmpSet.Fracture( PM_FAST );
m_triangulatedPolys.clear();
m_triangulationValid = true;
while( tmpSet.OutlineCount() > 0 )
{
m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>() );
PolygonTriangulation tess( *m_triangulatedPolys.back() );
// If the tesselation fails, we re-fracture the polygon, which will
// first simplify the system before fracturing and removing the holes
// This may result in multiple, disjoint polygons.
if( !tess.TesselatePolygon( tmpSet.Polygon( 0 ).front() ) )
{
tmpSet.Fracture( PM_FAST );
m_triangulationValid = false;
continue;
}
tmpSet.DeletePolygon( 0 );
m_triangulationValid = true;
}
if( m_triangulationValid )
m_hash = checksum();
}
MD5_HASH SHAPE_POLY_SET::checksum() const
{
MD5_HASH hash;
hash.Hash( m_polys.size() );
for( const auto& outline : m_polys )
{
hash.Hash( outline.size() );
for( const auto& lc : outline )
{
hash.Hash( lc.PointCount() );
for( int i = 0; i < lc.PointCount(); i++ )
{
hash.Hash( lc.CPoint( i ).x );
hash.Hash( lc.CPoint( i ).y );
}
}
}
hash.Finalize();
return hash;
}
bool SHAPE_POLY_SET::HasTouchingHoles() const
{
for( int i = 0; i < OutlineCount(); i++ )
{
if( hasTouchingHoles( CPolygon( i ) ) )
{
return true;
}
}
return false;
}
bool SHAPE_POLY_SET::hasTouchingHoles( const POLYGON& aPoly ) const
{
std::set< long long > ptHashes;
for( const auto& lc : aPoly )
{
for( const VECTOR2I& pt : lc.CPoints() )
{
const long long ptHash = (long long) pt.x << 32 | pt.y;
if( ptHashes.count( ptHash ) > 0 )
{
return true;
}
ptHashes.insert( ptHash );
}
}
return false;
}