mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-15 10:43:15 +02:00
Tesselation can fail for a number of reasons. When this happens, we set the triangulationValid flag to false to prevent using the broken triangulation. This will fall back to the slow OpenGL triangulation when DrawPolygon is called.
1961 lines
50 KiB
C++
1961 lines
50 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2015-2017 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::Inflate( int aFactor, int aCircleSegmentsCount )
|
|
{
|
|
// A static table to avoid repetitive calculations of the coefficient
|
|
// 1.0 - cos( M_PI/aCircleSegmentsCount)
|
|
// aCircleSegmentsCount is most of time <= 64 and usually 8, 12, 16, 32
|
|
#define SEG_CNT_MAX 64
|
|
static double arc_tolerance_factor[SEG_CNT_MAX + 1];
|
|
|
|
ClipperOffset c;
|
|
|
|
for( const POLYGON& poly : m_polys )
|
|
{
|
|
for( size_t i = 0; i < poly.size(); i++ )
|
|
c.AddPath( poly[i].convertToClipper( i == 0 ), jtRound, etClosedPolygon );
|
|
}
|
|
|
|
PolyTree solution;
|
|
|
|
// Calculate the arc tolerance (arc error) from the seg count by circle.
|
|
// the seg count is nn = M_PI / acos(1.0 - c.ArcTolerance / abs(aFactor))
|
|
// see:
|
|
// www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
|
|
|
|
if( aCircleSegmentsCount < 6 ) // avoid incorrect aCircleSegmentsCount values
|
|
aCircleSegmentsCount = 6;
|
|
|
|
double coeff;
|
|
|
|
if( aCircleSegmentsCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegmentsCount] == 0 )
|
|
{
|
|
coeff = 1.0 - cos( M_PI / aCircleSegmentsCount );
|
|
|
|
if( aCircleSegmentsCount <= SEG_CNT_MAX )
|
|
arc_tolerance_factor[aCircleSegmentsCount] = coeff;
|
|
}
|
|
else
|
|
coeff = arc_tolerance_factor[aCircleSegmentsCount];
|
|
|
|
c.ArcTolerance = std::abs( aFactor ) * coeff;
|
|
|
|
c.Execute( solution, aFactor );
|
|
|
|
importTree( &solution );
|
|
}
|
|
|
|
|
|
void SHAPE_POLY_SET::importTree( PolyTree* tree )
|
|
{
|
|
m_polys.clear();
|
|
|
|
for( PolyNode* n = tree->GetFirst(); n; n = n->GetNext() )
|
|
{
|
|
if( !n->IsHole() )
|
|
{
|
|
POLYGON paths;
|
|
paths.reserve( n->Childs.size() + 1 );
|
|
paths.push_back( 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 );
|
|
|
|
// 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;
|
|
|
|
for( int i = 0; i < tmpSet.OutlineCount(); i++ )
|
|
{
|
|
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
|
|
if( !tess.TesselatePolygon( tmpSet.Polygon( i ).front() ) )
|
|
{
|
|
tmpSet.Fracture( PM_FAST );
|
|
|
|
if( !tess.TesselatePolygon( tmpSet.Polygon( i ).front() ) )
|
|
{
|
|
m_triangulatedPolys.pop_back();
|
|
m_triangulationValid = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|