2015-06-12 17:12:02 +02:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
2019-01-26 13:01:48 +01:00
|
|
|
* Copyright (C) 2015-2019 CERN
|
2022-01-20 20:54:22 +00:00
|
|
|
* Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
|
2022-02-09 09:01:30 -05:00
|
|
|
*
|
2015-06-12 17:12:02 +02:00
|
|
|
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
|
2017-03-07 13:06:00 +01:00
|
|
|
* @author Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
|
2015-06-12 17:12:02 +02:00
|
|
|
*
|
2015-07-27 21:45:57 +02:00
|
|
|
* Point in polygon algorithm adapted from Clipper Library (C) Angus Johnson,
|
|
|
|
* subject to Clipper library license.
|
|
|
|
*
|
2015-06-12 17:12:02 +02:00
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <assert.h> // for assert
|
|
|
|
#include <cmath> // for sqrt, cos, hypot, isinf
|
2015-06-12 17:12:02 +02:00
|
|
|
#include <cstdio>
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <istream> // for operator<<, operator>>
|
|
|
|
#include <limits> // for numeric_limits
|
2021-06-01 21:48:16 +01:00
|
|
|
#include <map>
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <memory>
|
2015-06-12 17:12:02 +02:00
|
|
|
#include <set>
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <string> // for char_traits, operator!=
|
|
|
|
#include <type_traits> // for swap, move
|
2017-11-23 17:20:27 +01:00
|
|
|
#include <unordered_set>
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <vector>
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <clipper.hpp> // for Clipper, PolyNode, Clipp...
|
2018-03-22 18:02:45 +00:00
|
|
|
#include <geometry/geometry_utils.h>
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <geometry/polygon_triangulation.h>
|
|
|
|
#include <geometry/seg.h> // for SEG, OPT_VECTOR2I
|
2015-07-27 21:45:57 +02:00
|
|
|
#include <geometry/shape.h>
|
|
|
|
#include <geometry/shape_line_chain.h>
|
|
|
|
#include <geometry/shape_poly_set.h>
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <math/box2.h> // for BOX2I
|
2020-01-07 23:27:29 +00:00
|
|
|
#include <math/util.h> // for KiROUND, rescale
|
2020-01-07 17:12:59 +00:00
|
|
|
#include <math/vector2d.h> // for VECTOR2I, VECTOR2D, VECTOR2
|
|
|
|
#include <md5_hash.h>
|
2020-10-22 08:36:28 -04:00
|
|
|
#include <geometry/shape_segment.h>
|
|
|
|
#include <geometry/shape_circle.h>
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2021-09-21 10:47:30 -07:00
|
|
|
#include <wx/log.h>
|
|
|
|
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
SHAPE_POLY_SET::SHAPE_POLY_SET() :
|
|
|
|
SHAPE( SH_POLY_SET )
|
|
|
|
{
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
|
2021-09-25 15:31:19 +01:00
|
|
|
SHAPE_POLY_SET::SHAPE_POLY_SET( const BOX2D& aRect ) :
|
|
|
|
SHAPE( SH_POLY_SET )
|
|
|
|
{
|
|
|
|
NewOutline();
|
|
|
|
Append( VECTOR2I( aRect.GetLeft(), aRect.GetTop() ) );
|
|
|
|
Append( VECTOR2I( aRect.GetRight(), aRect.GetTop() ) );
|
|
|
|
Append( VECTOR2I( aRect.GetRight(), aRect.GetBottom() ) );
|
|
|
|
Append( VECTOR2I( aRect.GetLeft(), aRect.GetBottom() ) );
|
|
|
|
Outline( 0 ).SetClosed( true );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-14 17:57:18 +01:00
|
|
|
SHAPE_POLY_SET::SHAPE_POLY_SET( const SHAPE_LINE_CHAIN& aOutline ) :
|
|
|
|
SHAPE( SH_POLY_SET )
|
|
|
|
{
|
|
|
|
AddOutline( aOutline );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-17 19:29:21 -07:00
|
|
|
SHAPE_POLY_SET::SHAPE_POLY_SET( const SHAPE_POLY_SET& aOther ) :
|
2021-06-03 00:01:36 +01:00
|
|
|
SHAPE( aOther ),
|
|
|
|
m_polys( aOther.m_polys )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2018-09-20 21:23:15 -07:00
|
|
|
if( aOther.IsTriangulationUpToDate() )
|
|
|
|
{
|
|
|
|
for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
|
2021-06-03 00:01:36 +01:00
|
|
|
{
|
|
|
|
const TRIANGULATED_POLYGON* poly = aOther.TriangulatedPolygon( i );
|
|
|
|
m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( *poly ) );
|
|
|
|
}
|
2018-09-20 21:23:15 -07:00
|
|
|
|
|
|
|
m_hash = aOther.GetHash();
|
|
|
|
m_triangulationValid = true;
|
|
|
|
}
|
2020-06-17 19:29:21 -07:00
|
|
|
else
|
|
|
|
{
|
|
|
|
m_triangulationValid = false;
|
|
|
|
m_hash = MD5_HASH();
|
|
|
|
m_triangulatedPolys.clear();
|
|
|
|
}
|
2015-07-27 21:45:57 +02:00
|
|
|
}
|
|
|
|
|
2018-02-03 10:39:46 +01:00
|
|
|
|
|
|
|
SHAPE_POLY_SET::~SHAPE_POLY_SET()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
SHAPE* SHAPE_POLY_SET::Clone() const
|
|
|
|
{
|
|
|
|
return new SHAPE_POLY_SET( *this );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool SHAPE_POLY_SET::GetRelativeIndices( int aGlobalIdx,
|
2021-07-18 10:06:48 -04:00
|
|
|
SHAPE_POLY_SET::VERTEX_INDEX* aRelativeIndices ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
int polygonIdx = 0;
|
|
|
|
unsigned int contourIdx = 0;
|
|
|
|
int vertexIdx = 0;
|
|
|
|
|
|
|
|
int currentGlobalIdx = 0;
|
|
|
|
|
|
|
|
for( polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
|
|
|
|
{
|
2020-04-13 16:49:30 +02:00
|
|
|
const POLYGON& currentPolygon = CPolygon( polygonIdx );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
for( contourIdx = 0; contourIdx < currentPolygon.size(); contourIdx++ )
|
|
|
|
{
|
2020-04-13 16:49:30 +02:00
|
|
|
const SHAPE_LINE_CHAIN& currentContour = currentPolygon[contourIdx];
|
2017-03-07 13:06:00 +01:00
|
|
|
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,
|
2021-06-03 00:01:36 +01:00
|
|
|
int& aGlobalIdx ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2021-06-03 00:01:36 +01:00
|
|
|
int selectedVertex = aRelativeIndices.m_vertex;
|
|
|
|
unsigned int selectedContour = aRelativeIndices.m_contour;
|
|
|
|
unsigned int selectedPolygon = aRelativeIndices.m_polygon;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// Check whether the vertex indices make sense in this poly set
|
2017-11-23 17:20:27 +01:00
|
|
|
if( selectedPolygon < m_polys.size() && selectedContour < m_polys[selectedPolygon].size()
|
|
|
|
&& selectedVertex < m_polys[selectedPolygon][selectedContour].PointCount() )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
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 );
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
for( unsigned int contourIdx = 0; contourIdx < selectedContour; contourIdx++ )
|
2017-03-07 13:06:00 +01:00
|
|
|
aGlobalIdx += currentPolygon[contourIdx].PointCount();
|
|
|
|
|
|
|
|
aGlobalIdx += selectedVertex;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
int SHAPE_POLY_SET::NewOutline()
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-27 21:45:57 +02:00
|
|
|
SHAPE_LINE_CHAIN empty_path;
|
|
|
|
POLYGON poly;
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2016-10-18 14:53:05 +02:00
|
|
|
empty_path.SetClosed( true );
|
2015-07-06 15:15:48 +02:00
|
|
|
poly.push_back( empty_path );
|
|
|
|
m_polys.push_back( poly );
|
2015-06-12 17:12:02 +02:00
|
|
|
return m_polys.size() - 1;
|
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2015-06-12 17:12:02 +02:00
|
|
|
int SHAPE_POLY_SET::NewHole( int aOutline )
|
|
|
|
{
|
2016-10-18 14:53:05 +02:00
|
|
|
SHAPE_LINE_CHAIN empty_path;
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2016-10-18 14:53:05 +02:00
|
|
|
empty_path.SetClosed( true );
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
// 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 );
|
2015-07-27 21:45:57 +02:00
|
|
|
|
|
|
|
return m_polys.back().size() - 2;
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
int SHAPE_POLY_SET::Append( int x, int y, int aOutline, int aHole, bool aAllowDuplication )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2019-03-23 11:26:44 -07:00
|
|
|
assert( m_polys.size() );
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
if( aOutline < 0 )
|
2015-06-12 17:12:02 +02:00
|
|
|
aOutline += m_polys.size();
|
|
|
|
|
|
|
|
int idx;
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
if( aHole < 0 )
|
2015-06-12 17:12:02 +02:00
|
|
|
idx = 0;
|
|
|
|
else
|
|
|
|
idx = aHole + 1;
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
assert( aOutline < (int) m_polys.size() );
|
|
|
|
assert( idx < (int) m_polys[aOutline].size() );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
m_polys[aOutline][idx].Append( x, y, aAllowDuplication );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
return m_polys[aOutline][idx].PointCount();
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2020-08-14 01:10:40 +01:00
|
|
|
int SHAPE_POLY_SET::Append( SHAPE_ARC& aArc, int aOutline, int aHole )
|
|
|
|
{
|
|
|
|
assert( m_polys.size() );
|
|
|
|
|
|
|
|
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( aArc );
|
|
|
|
|
|
|
|
return m_polys[aOutline][idx].PointCount();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-07-26 19:47:26 -04:00
|
|
|
void SHAPE_POLY_SET::InsertVertex( int aGlobalIndex, const VECTOR2I& aNewVertex )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
VERTEX_INDEX index;
|
|
|
|
|
|
|
|
if( aGlobalIndex < 0 )
|
|
|
|
aGlobalIndex = 0;
|
|
|
|
|
2017-04-06 19:04:47 +02:00
|
|
|
if( aGlobalIndex >= TotalVertices() )
|
|
|
|
{
|
2017-03-07 13:06:00 +01:00
|
|
|
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" ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
int SHAPE_POLY_SET::VertexCount( int aOutline, int aHole ) const
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
if( m_polys.size() == 0 ) // Empty poly set
|
2017-10-25 09:39:26 +02:00
|
|
|
return 0;
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
if( aOutline < 0 ) // Use last outline
|
2015-06-12 17:12:02 +02:00
|
|
|
aOutline += m_polys.size();
|
|
|
|
|
|
|
|
int idx;
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
if( aHole < 0 )
|
2015-06-12 17:12:02 +02:00
|
|
|
idx = 0;
|
|
|
|
else
|
|
|
|
idx = aHole + 1;
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
if( aOutline >= (int) m_polys.size() ) // not existing outline
|
2017-10-25 09:39:26 +02:00
|
|
|
return 0;
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
if( idx >= (int) m_polys[aOutline].size() ) // not existing hole
|
2017-10-25 09:39:26 +02:00
|
|
|
return 0;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
return m_polys[aOutline][idx].PointCount();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-10-24 14:41:58 +02:00
|
|
|
int SHAPE_POLY_SET::FullPointCount() const
|
|
|
|
{
|
|
|
|
int full_count = 0;
|
|
|
|
|
|
|
|
if( m_polys.size() == 0 ) // Empty poly set
|
|
|
|
return full_count;
|
|
|
|
|
|
|
|
for( int ii = 0; ii < OutlineCount(); ii++ )
|
|
|
|
{
|
|
|
|
// the first polygon in m_polys[ii] is the main contour,
|
|
|
|
// only others are holes:
|
|
|
|
for( int idx = 0; idx <= HoleCount( ii ); idx++ )
|
|
|
|
{
|
|
|
|
full_count += m_polys[ii][idx].PointCount();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return full_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const VECTOR2I& SHAPE_POLY_SET::CVertex( int aIndex, int aOutline, int aHole ) const
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-06 15:15:48 +02:00
|
|
|
if( aOutline < 0 )
|
2015-06-12 17:12:02 +02:00
|
|
|
aOutline += m_polys.size();
|
|
|
|
|
|
|
|
int idx;
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
if( aHole < 0 )
|
2015-06-12 17:12:02 +02:00
|
|
|
idx = 0;
|
|
|
|
else
|
|
|
|
idx = aHole + 1;
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
assert( aOutline < (int) m_polys.size() );
|
|
|
|
assert( idx < (int) m_polys[aOutline].size() );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
return m_polys[aOutline][idx].CPoint( aIndex );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2017-04-16 18:32:47 +02:00
|
|
|
bool SHAPE_POLY_SET::GetNeighbourIndexes( int aGlobalIndex, int* aPrevious, int* aNext )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2017-04-16 18:32:47 +02:00
|
|
|
SHAPE_POLY_SET::VERTEX_INDEX index;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// If the edge does not exist, throw an exception, it is an illegal access memory error
|
2017-04-16 18:32:47 +02:00
|
|
|
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 )
|
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
index.m_vertex = lastpoint;
|
|
|
|
inext.m_vertex = 1;
|
2017-04-16 18:32:47 +02:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2017-04-16 18:32:47 +02:00
|
|
|
return true;
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-14 13:46:59 +01:00
|
|
|
bool SHAPE_POLY_SET::IsPolygonSelfIntersecting( int aPolygonIndex ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2019-07-14 13:46:59 +01:00
|
|
|
CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
|
|
|
|
CONST_SEGMENT_ITERATOR innerIterator;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2019-07-14 13:46:59 +01:00
|
|
|
for( iterator = CIterateSegmentsWithHoles( aPolygonIndex ); iterator; iterator++ )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-14 13:46:59 +01:00
|
|
|
bool SHAPE_POLY_SET::IsSelfIntersecting() const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
for( unsigned int polygon = 0; polygon < m_polys.size(); polygon++ )
|
|
|
|
{
|
|
|
|
if( IsPolygonSelfIntersecting( polygon ) )
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2015-06-12 17:12:02 +02:00
|
|
|
int SHAPE_POLY_SET::AddOutline( const SHAPE_LINE_CHAIN& aOutline )
|
|
|
|
{
|
2015-07-06 15:15:48 +02:00
|
|
|
assert( aOutline.IsClosed() );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
POLYGON poly;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
poly.push_back( aOutline );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
|
|
|
m_polys.push_back( poly );
|
|
|
|
|
|
|
|
return m_polys.size() - 1;
|
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
|
|
|
int SHAPE_POLY_SET::AddHole( const SHAPE_LINE_CHAIN& aHole, int aOutline )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
assert( m_polys.size() );
|
2015-07-06 15:15:48 +02:00
|
|
|
|
|
|
|
if( aOutline < 0 )
|
2015-06-12 17:12:02 +02:00
|
|
|
aOutline += m_polys.size();
|
|
|
|
|
2020-03-12 14:42:32 +01:00
|
|
|
assert( aOutline < (int)m_polys.size() );
|
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
POLYGON& poly = m_polys[aOutline];
|
2015-07-06 15:15:48 +02:00
|
|
|
|
|
|
|
assert( poly.size() );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
poly.push_back( aHole );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2020-07-18 07:03:40 -07:00
|
|
|
return poly.size() - 2;
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2021-02-08 10:40:32 +00:00
|
|
|
double SHAPE_POLY_SET::Area()
|
|
|
|
{
|
|
|
|
double area = 0.0;
|
|
|
|
|
|
|
|
for( int i = 0; i < OutlineCount(); i++ )
|
|
|
|
{
|
|
|
|
area += Outline( i ).Area();
|
|
|
|
|
|
|
|
for( int j = 0; j < HoleCount( i ); j++ )
|
|
|
|
area -= Hole( i, j ).Area();
|
|
|
|
}
|
|
|
|
|
|
|
|
return area;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-06-19 15:53:49 +01:00
|
|
|
int SHAPE_POLY_SET::ArcCount() const
|
|
|
|
{
|
|
|
|
int retval = 0;
|
|
|
|
|
|
|
|
for( const POLYGON& poly : m_polys )
|
|
|
|
{
|
|
|
|
for( size_t i = 0; i < poly.size(); i++ )
|
|
|
|
retval += poly[i].ArcCount();
|
|
|
|
}
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-05-04 20:13:41 +01:00
|
|
|
void SHAPE_POLY_SET::GetArcs( std::vector<SHAPE_ARC>& aArcBuffer ) const
|
|
|
|
{
|
|
|
|
for( const POLYGON& poly : m_polys )
|
|
|
|
{
|
|
|
|
for( size_t i = 0; i < poly.size(); i++ )
|
|
|
|
{
|
|
|
|
for( SHAPE_ARC arc : poly[i].m_arcs )
|
|
|
|
aArcBuffer.push_back( arc );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-06-19 15:53:49 +01:00
|
|
|
void SHAPE_POLY_SET::ClearArcs()
|
|
|
|
{
|
|
|
|
for( POLYGON& poly : m_polys )
|
|
|
|
{
|
|
|
|
for( size_t i = 0; i < poly.size(); i++ )
|
|
|
|
poly[i].ClearArcs();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-06-15 11:28:36 +02:00
|
|
|
void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aOtherShape,
|
2021-06-03 00:01:36 +01:00
|
|
|
POLYGON_MODE aFastMode )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2018-09-12 03:19:11 -07:00
|
|
|
booleanOp( aType, *this, aOtherShape, aFastMode );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
void SHAPE_POLY_SET::booleanOp( ClipperLib::ClipType aType, const SHAPE_POLY_SET& aShape,
|
|
|
|
const SHAPE_POLY_SET& aOtherShape, POLYGON_MODE aFastMode )
|
2015-08-24 09:55:22 -04:00
|
|
|
{
|
2021-06-19 15:53:49 +01:00
|
|
|
if( ( aShape.OutlineCount() > 1 || aOtherShape.OutlineCount() > 0 )
|
|
|
|
&& ( aShape.ArcCount() > 0 || aOtherShape.ArcCount() > 0 ) )
|
|
|
|
{
|
2022-02-09 09:01:30 -05:00
|
|
|
wxFAIL_MSG( wxT( "Boolean ops on curved polygons are not supported. You should call "
|
|
|
|
"ClearArcs() before carrying out the boolean operation." ) );
|
2021-06-19 15:53:49 +01:00
|
|
|
}
|
|
|
|
|
2020-11-18 15:39:14 -08:00
|
|
|
ClipperLib::Clipper c;
|
2015-08-24 09:55:22 -04:00
|
|
|
|
2018-09-12 03:19:11 -07:00
|
|
|
c.StrictlySimple( aFastMode == PM_STRICTLY_SIMPLE );
|
2015-08-24 09:55:22 -04:00
|
|
|
|
2021-06-19 10:56:22 +01:00
|
|
|
std::vector<CLIPPER_Z_VALUE> zValues;
|
|
|
|
std::vector<SHAPE_ARC> arcBuffer;
|
2021-06-01 21:48:16 +01:00
|
|
|
std::map<VECTOR2I, CLIPPER_Z_VALUE> newIntersectPoints;
|
2021-05-04 20:13:41 +01:00
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
for( const POLYGON& poly : aShape.m_polys )
|
2015-08-24 09:55:22 -04:00
|
|
|
{
|
2021-06-19 10:56:22 +01:00
|
|
|
for( size_t i = 0; i < poly.size(); i++ )
|
|
|
|
{
|
|
|
|
c.AddPath( poly[i].convertToClipper( i == 0, zValues, arcBuffer ),
|
|
|
|
ClipperLib::ptSubject, true );
|
|
|
|
}
|
2015-08-24 09:55:22 -04:00
|
|
|
}
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
for( const POLYGON& poly : aOtherShape.m_polys )
|
2015-08-24 09:55:22 -04:00
|
|
|
{
|
2018-09-12 03:19:11 -07:00
|
|
|
for( size_t i = 0; i < poly.size(); i++ )
|
2021-06-19 10:56:22 +01:00
|
|
|
{
|
|
|
|
c.AddPath( poly[i].convertToClipper( i == 0, zValues, arcBuffer ),
|
|
|
|
ClipperLib::ptClip, true );
|
|
|
|
}
|
2015-08-24 09:55:22 -04:00
|
|
|
}
|
|
|
|
|
2020-11-18 15:39:14 -08:00
|
|
|
ClipperLib::PolyTree solution;
|
2015-08-24 09:55:22 -04:00
|
|
|
|
2021-06-19 10:56:22 +01:00
|
|
|
ClipperLib::ZFillCallback callback =
|
2021-06-01 21:48:16 +01:00
|
|
|
[&]( ClipperLib::IntPoint & e1bot, ClipperLib::IntPoint & e1top,
|
2021-06-19 10:56:22 +01:00
|
|
|
ClipperLib::IntPoint & e2bot, ClipperLib::IntPoint & e2top,
|
|
|
|
ClipperLib::IntPoint & pt )
|
|
|
|
{
|
2021-06-01 21:48:16 +01:00
|
|
|
auto arcIndex =
|
|
|
|
[&]( const ssize_t& aZvalue, const ssize_t& aCompareVal = -1 ) -> ssize_t
|
|
|
|
{
|
|
|
|
ssize_t retval;
|
|
|
|
|
|
|
|
retval = zValues.at( aZvalue ).m_SecondArcIdx;
|
|
|
|
|
|
|
|
if( retval == -1 || ( aCompareVal > 0 && retval != aCompareVal ) )
|
|
|
|
retval = zValues.at( aZvalue ).m_FirstArcIdx;
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
};
|
|
|
|
|
|
|
|
auto arcSegment =
|
|
|
|
[&]( const ssize_t& aBottomZ, const ssize_t aTopZ ) -> ssize_t
|
|
|
|
{
|
|
|
|
ssize_t retval = arcIndex( aBottomZ );
|
|
|
|
|
|
|
|
if( retval != -1 )
|
|
|
|
{
|
|
|
|
if( retval != arcIndex( aTopZ, retval ) )
|
|
|
|
retval = -1; // Not an arc segment as the two indices do not match
|
|
|
|
}
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
};
|
|
|
|
|
|
|
|
ssize_t e1ArcSegmentIndex = arcSegment( e1bot.Z, e1top.Z );
|
|
|
|
ssize_t e2ArcSegmentIndex = arcSegment( e2bot.Z, e2top.Z );
|
|
|
|
|
|
|
|
CLIPPER_Z_VALUE newZval;
|
|
|
|
|
|
|
|
if( e1ArcSegmentIndex != -1 )
|
|
|
|
{
|
|
|
|
newZval.m_FirstArcIdx = e1ArcSegmentIndex;
|
|
|
|
newZval.m_SecondArcIdx = e2ArcSegmentIndex;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
newZval.m_FirstArcIdx = e2ArcSegmentIndex;
|
|
|
|
newZval.m_SecondArcIdx = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t z_value_ptr = zValues.size();
|
|
|
|
zValues.push_back( newZval );
|
|
|
|
|
|
|
|
// Only worry about arc segments for later processing
|
|
|
|
if( newZval.m_FirstArcIdx != -1 )
|
|
|
|
newIntersectPoints.insert( { VECTOR2I( pt.X, pt.Y ), newZval } );
|
|
|
|
|
|
|
|
pt.Z = z_value_ptr;
|
2021-07-03 15:43:40 -04:00
|
|
|
//@todo amend X,Y values to true intersection between arcs or arc and segment
|
2021-06-19 10:56:22 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
c.ZFillFunction( callback ); // register callback
|
2015-08-24 09:55:22 -04:00
|
|
|
|
2021-06-19 10:56:22 +01:00
|
|
|
c.Execute( aType, solution, ClipperLib::pftNonZero, ClipperLib::pftNonZero );
|
2021-05-04 20:13:41 +01:00
|
|
|
|
2021-06-19 10:56:22 +01:00
|
|
|
importTree( &solution, zValues, arcBuffer );
|
2021-05-04 20:13:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-12-15 21:21:25 +01:00
|
|
|
void SHAPE_POLY_SET::BooleanAdd( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2020-11-18 15:39:14 -08:00
|
|
|
booleanOp( ClipperLib::ctUnion, b, aFastMode );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2015-12-15 21:21:25 +01:00
|
|
|
void SHAPE_POLY_SET::BooleanSubtract( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2020-11-18 15:39:14 -08:00
|
|
|
booleanOp( ClipperLib::ctDifference, b, aFastMode );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-12-15 21:21:25 +01:00
|
|
|
void SHAPE_POLY_SET::BooleanIntersection( const SHAPE_POLY_SET& b, POLYGON_MODE aFastMode )
|
2015-08-24 09:55:22 -04:00
|
|
|
{
|
2020-11-18 15:39:14 -08:00
|
|
|
booleanOp( ClipperLib::ctIntersection, b, aFastMode );
|
2015-08-24 09:55:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
void SHAPE_POLY_SET::BooleanAdd( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b,
|
|
|
|
POLYGON_MODE aFastMode )
|
2015-08-24 09:55:22 -04:00
|
|
|
{
|
2020-11-18 15:39:14 -08:00
|
|
|
booleanOp( ClipperLib::ctUnion, a, b, aFastMode );
|
2015-08-24 09:55:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
void SHAPE_POLY_SET::BooleanSubtract( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b,
|
|
|
|
POLYGON_MODE aFastMode )
|
2015-08-24 09:55:22 -04:00
|
|
|
{
|
2020-11-18 15:39:14 -08:00
|
|
|
booleanOp( ClipperLib::ctDifference, a, b, aFastMode );
|
2015-08-24 09:55:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
void SHAPE_POLY_SET::BooleanIntersection( const SHAPE_POLY_SET& a, const SHAPE_POLY_SET& b,
|
|
|
|
POLYGON_MODE aFastMode )
|
2015-08-24 09:55:22 -04:00
|
|
|
{
|
2020-11-18 15:39:14 -08:00
|
|
|
booleanOp( ClipperLib::ctIntersection, a, b, aFastMode );
|
2015-08-24 09:55:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-16 16:41:21 +01:00
|
|
|
void SHAPE_POLY_SET::InflateWithLinkedHoles( int aFactor, int aCircleSegmentsCount,
|
|
|
|
POLYGON_MODE aFastMode )
|
2019-05-09 16:23:18 +02:00
|
|
|
{
|
|
|
|
Simplify( aFastMode );
|
|
|
|
Inflate( aFactor, aCircleSegmentsCount );
|
|
|
|
Fracture( aFastMode );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
void SHAPE_POLY_SET::Inflate( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2020-11-18 15:39:14 -08:00
|
|
|
using namespace ClipperLib;
|
2016-01-14 12:37:09 +01:00
|
|
|
// A static table to avoid repetitive calculations of the coefficient
|
2021-06-03 00:01:36 +01:00
|
|
|
// 1.0 - cos( M_PI / aCircleSegCount )
|
|
|
|
// aCircleSegCount is most of time <= 64 and usually 8, 12, 16, 32
|
2016-01-14 12:37:09 +01:00
|
|
|
#define SEG_CNT_MAX 64
|
2017-11-23 17:20:27 +01:00
|
|
|
static double arc_tolerance_factor[SEG_CNT_MAX + 1];
|
2016-01-14 12:37:09 +01:00
|
|
|
|
2015-06-12 17:12:02 +02:00
|
|
|
ClipperOffset c;
|
|
|
|
|
2019-07-16 16:41:21 +01:00
|
|
|
// N.B. see the Clipper documentation for jtSquare/jtMiter/jtRound. They are poorly named
|
|
|
|
// and are not what you'd think they are.
|
|
|
|
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
|
2020-02-16 09:47:33 +01:00
|
|
|
JoinType joinType = jtRound; // The way corners are offsetted
|
|
|
|
double miterLimit = 2.0; // Smaller value when using jtMiter for joinType
|
|
|
|
JoinType miterFallback = jtSquare;
|
2020-01-26 18:01:11 +01:00
|
|
|
|
|
|
|
switch( aCornerStrategy )
|
|
|
|
{
|
|
|
|
case ALLOW_ACUTE_CORNERS:
|
|
|
|
joinType = jtMiter;
|
|
|
|
miterLimit = 10; // Allows large spikes
|
|
|
|
miterFallback = jtSquare;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
|
|
|
|
joinType = jtMiter;
|
|
|
|
miterFallback = jtRound;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ROUND_ACUTE_CORNERS: // Acute angles are rounded
|
|
|
|
joinType = jtMiter;
|
|
|
|
miterFallback = jtSquare;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CHAMFER_ALL_CORNERS: // All angles are chamfered.
|
|
|
|
joinType = jtSquare;
|
|
|
|
miterFallback = jtSquare;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ROUND_ALL_CORNERS: // All angles are rounded.
|
|
|
|
joinType = jtRound;
|
|
|
|
miterFallback = jtSquare;
|
|
|
|
break;
|
|
|
|
}
|
2019-05-20 19:36:12 -07:00
|
|
|
|
2021-06-19 10:56:22 +01:00
|
|
|
std::vector<CLIPPER_Z_VALUE> zValues;
|
|
|
|
std::vector<SHAPE_ARC> arcBuffer;
|
|
|
|
|
2016-06-29 16:07:55 -04:00
|
|
|
for( const POLYGON& poly : m_polys )
|
2015-07-27 21:45:57 +02:00
|
|
|
{
|
2018-09-12 03:19:11 -07:00
|
|
|
for( size_t i = 0; i < poly.size(); i++ )
|
2021-06-19 10:56:22 +01:00
|
|
|
{
|
|
|
|
c.AddPath( poly[i].convertToClipper( i == 0, zValues, arcBuffer ),
|
|
|
|
joinType, etClosedPolygon );
|
|
|
|
}
|
2015-07-27 21:45:57 +02:00
|
|
|
}
|
2015-06-12 17:12:02 +02:00
|
|
|
|
|
|
|
PolyTree solution;
|
|
|
|
|
2019-07-16 16:41:21 +01:00
|
|
|
// 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(aAmount))
|
|
|
|
// http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/Properties/ArcTolerance.htm
|
2016-01-14 12:37:09 +01:00
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
if( aCircleSegCount < 6 ) // avoid incorrect aCircleSegCount values
|
|
|
|
aCircleSegCount = 6;
|
2016-01-14 12:37:09 +01:00
|
|
|
|
|
|
|
double coeff;
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
if( aCircleSegCount > SEG_CNT_MAX || arc_tolerance_factor[aCircleSegCount] == 0 )
|
2016-01-14 12:37:09 +01:00
|
|
|
{
|
2021-06-03 00:01:36 +01:00
|
|
|
coeff = 1.0 - cos( M_PI / aCircleSegCount );
|
2016-01-14 12:37:09 +01:00
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
if( aCircleSegCount <= SEG_CNT_MAX )
|
|
|
|
arc_tolerance_factor[aCircleSegCount] = coeff;
|
2016-01-14 12:37:09 +01:00
|
|
|
}
|
|
|
|
else
|
2021-06-03 00:01:36 +01:00
|
|
|
{
|
|
|
|
coeff = arc_tolerance_factor[aCircleSegCount];
|
|
|
|
}
|
2016-01-14 12:37:09 +01:00
|
|
|
|
2019-07-16 16:41:21 +01:00
|
|
|
c.ArcTolerance = std::abs( aAmount ) * coeff;
|
2019-07-13 23:34:09 +01:00
|
|
|
c.MiterLimit = miterLimit;
|
2019-07-16 16:41:21 +01:00
|
|
|
c.MiterFallback = miterFallback;
|
|
|
|
c.Execute( solution, aAmount );
|
2015-07-14 22:23:13 +02:00
|
|
|
|
2021-06-19 10:56:22 +01:00
|
|
|
importTree( &solution, zValues, arcBuffer );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2021-06-19 10:56:22 +01:00
|
|
|
void SHAPE_POLY_SET::importTree( ClipperLib::PolyTree* tree,
|
|
|
|
const std::vector<CLIPPER_Z_VALUE>& aZValueBuffer,
|
|
|
|
const std::vector<SHAPE_ARC>& aArcBuffer )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
|
|
|
m_polys.clear();
|
|
|
|
|
2020-11-18 15:39:14 -08:00
|
|
|
for( ClipperLib::PolyNode* n = tree->GetFirst(); n; n = n->GetNext() )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
|
|
|
if( !n->IsHole() )
|
|
|
|
{
|
2015-07-27 21:45:57 +02:00
|
|
|
POLYGON paths;
|
2016-07-19 13:35:25 -04:00
|
|
|
paths.reserve( n->Childs.size() + 1 );
|
2020-08-14 01:10:40 +01:00
|
|
|
|
2021-06-19 10:56:22 +01:00
|
|
|
paths.emplace_back( n->Contour, aZValueBuffer, aArcBuffer );
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
for( unsigned int i = 0; i < n->Childs.size(); i++ )
|
2021-06-19 10:56:22 +01:00
|
|
|
paths.emplace_back( n->Childs[i]->Contour, aZValueBuffer, aArcBuffer );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
m_polys.push_back( paths );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2015-06-12 17:12:02 +02:00
|
|
|
struct FractureEdge
|
|
|
|
{
|
2015-07-27 21:45:57 +02:00
|
|
|
FractureEdge( int y = 0 ) :
|
2015-07-06 15:15:48 +02:00
|
|
|
m_connected( false ),
|
2021-07-18 10:06:48 -04:00
|
|
|
m_next( nullptr )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-27 21:45:57 +02:00
|
|
|
m_p1.x = m_p2.y = y;
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
FractureEdge( bool connected, const VECTOR2I& p1, const VECTOR2I& p2 ) :
|
2015-07-06 15:15:48 +02:00
|
|
|
m_connected( connected ),
|
|
|
|
m_p1( p1 ),
|
|
|
|
m_p2( p2 ),
|
2021-07-18 10:06:48 -04:00
|
|
|
m_next( nullptr )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
bool matches( int y ) const
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2019-06-22 17:32:12 +01:00
|
|
|
return ( y >= m_p1.y || y >= m_p2.y ) && ( y <= m_p1.y || y <= m_p2.y );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
bool m_connected;
|
|
|
|
VECTOR2I m_p1;
|
|
|
|
VECTOR2I m_p2;
|
2015-07-06 15:15:48 +02:00
|
|
|
FractureEdge* m_next;
|
2015-06-12 17:12:02 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2015-06-22 09:09:44 +02:00
|
|
|
typedef std::vector<FractureEdge*> FractureEdgeSet;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
static int processEdge( FractureEdgeSet& edges, FractureEdge* edge )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
int x = edge->m_p1.x;
|
|
|
|
int y = edge->m_p1.y;
|
|
|
|
int min_dist = std::numeric_limits<int>::max();
|
|
|
|
int x_nearest = 0;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2021-07-18 10:06:48 -04:00
|
|
|
FractureEdge* e_nearest = nullptr;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2020-10-20 21:30:31 +01:00
|
|
|
for( FractureEdge* e : edges )
|
2015-07-06 15:15:48 +02:00
|
|
|
{
|
2020-10-20 21:30:31 +01:00
|
|
|
if( !e->matches( y ) )
|
2015-07-06 15:15:48 +02:00
|
|
|
continue;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
int x_intersect;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2020-10-20 21:30:31 +01:00
|
|
|
if( e->m_p1.y == e->m_p2.y ) // horizontal edge
|
|
|
|
{
|
|
|
|
x_intersect = std::max( e->m_p1.x, e->m_p2.x );
|
|
|
|
}
|
2015-07-06 15:15:48 +02:00
|
|
|
else
|
2020-10-20 21:30:31 +01:00
|
|
|
{
|
|
|
|
x_intersect = e->m_p1.x + rescale( e->m_p2.x - e->m_p1.x, y - e->m_p1.y,
|
|
|
|
e->m_p2.y - e->m_p1.y );
|
|
|
|
}
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
int dist = ( x - x_intersect );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2020-10-20 21:30:31 +01:00
|
|
|
if( dist >= 0 && dist < min_dist && e->m_connected )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
min_dist = dist;
|
|
|
|
x_nearest = x_intersect;
|
2020-10-20 21:30:31 +01:00
|
|
|
e_nearest = e;
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
if( e_nearest && e_nearest->m_connected )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-06 15:15:48 +02:00
|
|
|
int count = 0;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2020-10-20 21:30:31 +01:00
|
|
|
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 );
|
2015-06-22 09:09:44 +02:00
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
edges.push_back( split_2 );
|
|
|
|
edges.push_back( lead1 );
|
|
|
|
edges.push_back( lead2 );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
FractureEdge* link = e_nearest->m_next;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
e_nearest->m_p2 = VECTOR2I( x_nearest, y );
|
2015-07-06 15:15:48 +02:00
|
|
|
e_nearest->m_next = lead1;
|
2015-06-12 17:12:02 +02:00
|
|
|
lead1->m_next = edge;
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
FractureEdge* last;
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
for( last = edge; last->m_next != edge; last = last->m_next )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-06 15:15:48 +02:00
|
|
|
last->m_connected = true;
|
|
|
|
count++;
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
last->m_connected = true;
|
2017-11-23 17:20:27 +01:00
|
|
|
last->m_next = lead2;
|
|
|
|
lead2->m_next = split_2;
|
2015-06-12 17:12:02 +02:00
|
|
|
split_2->m_next = link;
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
return count + 1;
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
void SHAPE_POLY_SET::fractureSingle( POLYGON& paths )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
|
|
|
FractureEdgeSet edges;
|
2015-07-06 15:15:48 +02:00
|
|
|
FractureEdgeSet border_edges;
|
2021-07-18 10:06:48 -04:00
|
|
|
FractureEdge* root = nullptr;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
|
|
|
bool first = true;
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
if( paths.size() == 1 )
|
2015-06-12 17:12:02 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
int num_unconnected = 0;
|
|
|
|
|
2019-06-23 11:14:26 +01:00
|
|
|
for( const SHAPE_LINE_CHAIN& path : paths )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2019-06-23 11:14:26 +01:00
|
|
|
const std::vector<VECTOR2I>& points = path.CPoints();
|
|
|
|
int pointCount = points.size();
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2021-07-18 10:06:48 -04:00
|
|
|
FractureEdge* prev = nullptr, * first_edge = nullptr;
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
int x_min = std::numeric_limits<int>::max();
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2019-06-23 11:14:26 +01:00
|
|
|
for( int i = 0; i < pointCount; i++ )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2021-09-21 10:47:30 -07:00
|
|
|
if( points[i].x < x_min )
|
|
|
|
x_min = points[i].x;
|
|
|
|
|
2019-06-23 11:14:26 +01:00
|
|
|
// Do not use path.CPoint() here; open-coding it using the local variables "points"
|
|
|
|
// and "pointCount" gives a non-trivial performance boost to zone fill times.
|
|
|
|
FractureEdge* fe = new FractureEdge( first, points[ i ],
|
2021-07-18 10:06:48 -04:00
|
|
|
points[ i+1 == pointCount ? 0 : i+1 ] );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
if( !root )
|
2015-06-12 17:12:02 +02:00
|
|
|
root = fe;
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
if( !first_edge )
|
2015-06-12 17:12:02 +02:00
|
|
|
first_edge = fe;
|
2015-07-06 15:15:48 +02:00
|
|
|
|
|
|
|
if( prev )
|
2015-06-12 17:12:02 +02:00
|
|
|
prev->m_next = fe;
|
|
|
|
|
2019-06-23 11:14:26 +01:00
|
|
|
if( i == pointCount - 1 )
|
2015-06-12 17:12:02 +02:00
|
|
|
fe->m_next = first_edge;
|
|
|
|
|
|
|
|
prev = fe;
|
2015-07-06 15:15:48 +02:00
|
|
|
edges.push_back( fe );
|
|
|
|
|
|
|
|
if( !first )
|
|
|
|
{
|
2015-07-27 21:45:57 +02:00
|
|
|
if( fe->m_p1.x == x_min )
|
2015-07-06 15:15:48 +02:00
|
|
|
border_edges.push_back( fe );
|
|
|
|
}
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
if( !fe->m_connected )
|
2015-06-12 17:12:02 +02:00
|
|
|
num_unconnected++;
|
|
|
|
}
|
2017-11-23 17:20:27 +01:00
|
|
|
|
|
|
|
first = false; // first path is always the outline
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
// keep connecting holes to the main outline, until there's no holes left...
|
|
|
|
while( num_unconnected > 0 )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-27 21:45:57 +02:00
|
|
|
int x_min = std::numeric_limits<int>::max();
|
2021-09-21 10:47:30 -07:00
|
|
|
auto it = border_edges.begin();
|
2015-07-27 21:45:57 +02:00
|
|
|
|
2021-07-18 10:06:48 -04:00
|
|
|
FractureEdge* smallestX = nullptr;
|
2015-06-22 09:09:44 +02:00
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
// find the left-most hole edge and merge with the outline
|
2021-09-21 10:47:30 -07:00
|
|
|
for( ; it != border_edges.end(); ++it )
|
2015-06-22 09:09:44 +02:00
|
|
|
{
|
2021-09-21 10:47:30 -07:00
|
|
|
FractureEdge* border_edge = *it;
|
2019-06-23 11:14:26 +01:00
|
|
|
int xt = border_edge->m_p1.x;
|
2015-07-07 18:37:03 +02:00
|
|
|
|
2021-09-21 10:47:30 -07:00
|
|
|
if( ( xt <= x_min ) && !border_edge->m_connected )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-06 15:15:48 +02:00
|
|
|
x_min = xt;
|
2019-06-23 11:14:26 +01:00
|
|
|
smallestX = border_edge;
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
}
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2021-09-21 10:47:30 -07:00
|
|
|
int num_processed = processEdge( edges, smallestX );
|
|
|
|
|
|
|
|
// If we can't handle the edge, the zone is broken (maybe)
|
|
|
|
if( !num_processed )
|
|
|
|
{
|
2022-02-09 09:01:30 -05:00
|
|
|
wxLogWarning( wxT( "Broken polygon, dropping path" ) );
|
2021-09-21 10:47:30 -07:00
|
|
|
|
|
|
|
for( FractureEdge* edge : edges )
|
|
|
|
delete edge;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
num_unconnected -= num_processed;
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
paths.clear();
|
2015-07-27 21:45:57 +02:00
|
|
|
SHAPE_LINE_CHAIN newPath;
|
|
|
|
|
|
|
|
newPath.SetClosed( true );
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
FractureEdge* e;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
for( e = root; e->m_next != root; e = e->m_next )
|
2015-07-27 21:45:57 +02:00
|
|
|
newPath.Append( e->m_p1 );
|
2015-06-19 19:39:33 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
newPath.Append( e->m_p1 );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2019-06-23 11:14:26 +01:00
|
|
|
for( FractureEdge* edge : edges )
|
|
|
|
delete edge;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2019-06-23 11:14:26 +01:00
|
|
|
paths.push_back( std::move( newPath ) );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2015-12-15 21:21:25 +01:00
|
|
|
void SHAPE_POLY_SET::Fracture( POLYGON_MODE aFastMode )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
Simplify( aFastMode ); // remove overlapping holes/degeneracy
|
2015-07-27 21:45:57 +02:00
|
|
|
|
2016-06-29 16:07:55 -04:00
|
|
|
for( POLYGON& paths : m_polys )
|
2015-06-12 17:12:02 +02:00
|
|
|
fractureSingle( paths );
|
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
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
|
|
|
|
{
|
2018-01-02 00:34:37 +01:00
|
|
|
return (s1.A == s2.B && s1.B == s2.A);
|
2017-11-23 17:20:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool operator==( const EDGE& aOther ) const
|
|
|
|
{
|
|
|
|
return compareSegs( m_poly->CSegment( m_index ),
|
2021-06-03 00:01:36 +01:00
|
|
|
aOther.m_poly->CSegment( aOther.m_index ) );
|
2017-11-23 17:20:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool operator!=( const EDGE& aOther ) const
|
|
|
|
{
|
|
|
|
return !compareSegs( m_poly->CSegment( m_index ),
|
2021-06-03 00:01:36 +01:00
|
|
|
aOther.m_poly->CSegment( aOther.m_index ) );
|
2017-11-23 17:20:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
struct HASH
|
|
|
|
{
|
|
|
|
std::size_t operator()( const EDGE& aEdge ) const
|
|
|
|
{
|
2021-06-03 00:01:36 +01:00
|
|
|
const SEG& a = aEdge.m_poly->CSegment( aEdge.m_index );
|
2017-11-23 17:20:27 +01:00
|
|
|
|
|
|
|
return (std::size_t) ( a.A.x + a.B.x + a.A.y + a.B.y );
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
struct EDGE_LIST_ENTRY
|
|
|
|
{
|
2021-06-03 00:01:36 +01:00
|
|
|
int index;
|
2017-11-23 17:20:27 +01:00
|
|
|
EDGE_LIST_ENTRY* next;
|
|
|
|
};
|
|
|
|
|
|
|
|
std::unordered_set<EDGE, EDGE::HASH> uniqueEdges;
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
SHAPE_LINE_CHAIN lc = aPoly[0];
|
2017-11-23 17:20:27 +01:00
|
|
|
lc.Simplify();
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
auto edgeList = std::make_unique<EDGE_LIST_ENTRY[]>( lc.SegmentCount() );
|
2017-11-23 17:20:27 +01:00
|
|
|
|
|
|
|
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] );
|
|
|
|
}
|
|
|
|
|
2017-12-06 03:42:35 +01:00
|
|
|
auto edgeBuf = std::make_unique<EDGE_LIST_ENTRY* []>( lc.SegmentCount() );
|
2017-11-23 17:20:27 +01:00
|
|
|
|
|
|
|
int n = 0;
|
|
|
|
int outline = -1;
|
|
|
|
|
|
|
|
POLYGON result;
|
|
|
|
|
|
|
|
while( queue.size() )
|
|
|
|
{
|
2021-06-03 00:01:36 +01:00
|
|
|
EDGE_LIST_ENTRY* e_first = *queue.begin();
|
|
|
|
EDGE_LIST_ENTRY* e = e_first;
|
|
|
|
int cnt = 0;
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
do
|
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
edgeBuf[cnt++] = e;
|
|
|
|
e = e->next;
|
2018-01-02 00:34:37 +01:00
|
|
|
} while( e && e != e_first );
|
2017-11-23 17:20:27 +01:00
|
|
|
|
|
|
|
SHAPE_LINE_CHAIN outl;
|
|
|
|
|
|
|
|
for( int i = 0; i < cnt; i++ )
|
|
|
|
{
|
2021-06-03 00:01:36 +01:00
|
|
|
VECTOR2I p = lc.CPoint( edgeBuf[i]->index );
|
2017-11-23 17:20:27 +01:00
|
|
|
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++;
|
|
|
|
}
|
|
|
|
|
2018-02-03 10:39:46 +01:00
|
|
|
if( outline > 0 )
|
2017-11-23 17:20:27 +01:00
|
|
|
std::swap( result[0], result[outline] );
|
|
|
|
|
|
|
|
aPoly = result;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
void SHAPE_POLY_SET::Unfracture( POLYGON_MODE aFastMode )
|
|
|
|
{
|
|
|
|
for( POLYGON& path : m_polys )
|
|
|
|
unfractureSingle( path );
|
|
|
|
|
|
|
|
Simplify( aFastMode ); // remove overlapping holes/degeneracy
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-12-15 21:21:25 +01:00
|
|
|
void SHAPE_POLY_SET::Simplify( POLYGON_MODE aFastMode )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-27 21:45:57 +02:00
|
|
|
SHAPE_POLY_SET empty;
|
|
|
|
|
2020-11-18 15:39:14 -08:00
|
|
|
booleanOp( ClipperLib::ctUnion, empty, aFastMode );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
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 );
|
|
|
|
|
2021-07-18 10:06:48 -04:00
|
|
|
// If any hole, subtract it to main outline
|
2017-03-07 13:06:00 +01:00
|
|
|
if( holesBuffer.OutlineCount() )
|
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
holesBuffer.Simplify( SHAPE_POLY_SET::PM_FAST );
|
2017-03-07 13:06:00 +01:00
|
|
|
BooleanSubtract( holesBuffer, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
|
|
|
|
}
|
|
|
|
|
|
|
|
RemoveNullSegments();
|
|
|
|
|
|
|
|
return OutlineCount();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-06-12 17:12:02 +02:00
|
|
|
const std::string SHAPE_POLY_SET::Format() const
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
|
2020-10-23 01:11:59 +02:00
|
|
|
ss << "SHAPE_LINE_CHAIN poly; \n";
|
2015-06-12 17:12:02 +02:00
|
|
|
|
|
|
|
for( unsigned i = 0; i < m_polys.size(); i++ )
|
|
|
|
{
|
2017-03-07 13:06:00 +01:00
|
|
|
for( unsigned j = 0; j < m_polys[i].size(); j++ )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2020-10-23 01:11:59 +02:00
|
|
|
ss << "{ auto tmp = " << m_polys[i][j].Format() << ";\n";
|
|
|
|
|
|
|
|
SHAPE_POLY_SET poly;
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2020-10-23 01:11:59 +02:00
|
|
|
if( j == 0 )
|
|
|
|
{
|
|
|
|
ss << " poly.AddOutline(tmp); } \n";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ss << " poly.AddHole(tmp); } \n";
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2015-06-12 17:12:02 +02:00
|
|
|
bool SHAPE_POLY_SET::Parse( std::stringstream& aStream )
|
|
|
|
{
|
|
|
|
std::string tmp;
|
|
|
|
|
|
|
|
aStream >> tmp;
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
if( tmp != "polyset" )
|
2015-06-12 17:12:02 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
aStream >> tmp;
|
|
|
|
|
2015-06-15 16:01:43 +02:00
|
|
|
int n_polys = atoi( tmp.c_str() );
|
|
|
|
|
|
|
|
if( n_polys < 0 )
|
2015-06-12 17:12:02 +02:00
|
|
|
return false;
|
|
|
|
|
2015-06-15 16:01:43 +02:00
|
|
|
for( int i = 0; i < n_polys; i++ )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-27 21:45:57 +02:00
|
|
|
POLYGON paths;
|
|
|
|
|
2015-06-12 17:12:02 +02:00
|
|
|
aStream >> tmp;
|
2015-07-06 15:15:48 +02:00
|
|
|
|
|
|
|
if( tmp != "poly" )
|
2015-06-12 17:12:02 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
aStream >> tmp;
|
2015-06-15 16:01:43 +02:00
|
|
|
int n_outlines = atoi( tmp.c_str() );
|
|
|
|
|
|
|
|
if( n_outlines < 0 )
|
2015-06-12 17:12:02 +02:00
|
|
|
return false;
|
|
|
|
|
2015-06-15 16:01:43 +02:00
|
|
|
for( int j = 0; j < n_outlines; j++ )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-27 21:45:57 +02:00
|
|
|
SHAPE_LINE_CHAIN outline;
|
|
|
|
|
|
|
|
outline.SetClosed( true );
|
2015-06-12 17:12:02 +02:00
|
|
|
|
|
|
|
aStream >> tmp;
|
2015-06-15 16:01:43 +02:00
|
|
|
int n_vertices = atoi( tmp.c_str() );
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2015-06-15 16:01:43 +02:00
|
|
|
for( int v = 0; v < n_vertices; v++ )
|
2015-06-12 17:12:02 +02:00
|
|
|
{
|
2015-07-27 21:45:57 +02:00
|
|
|
VECTOR2I p;
|
2015-06-12 17:12:02 +02:00
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
aStream >> tmp; p.x = atoi( tmp.c_str() );
|
|
|
|
aStream >> tmp; p.y = atoi( tmp.c_str() );
|
|
|
|
outline.Append( p );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
2015-07-27 21:45:57 +02:00
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
paths.push_back( outline );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
2015-07-27 21:45:57 +02:00
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
m_polys.push_back( paths );
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2015-06-12 17:12:02 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-07-06 15:15:48 +02:00
|
|
|
|
2015-06-12 17:12:02 +02:00
|
|
|
const BOX2I SHAPE_POLY_SET::BBox( int aClearance ) const
|
|
|
|
{
|
|
|
|
BOX2I bb;
|
|
|
|
|
|
|
|
for( unsigned i = 0; i < m_polys.size(); i++ )
|
|
|
|
{
|
2015-07-29 14:18:53 +02:00
|
|
|
if( i == 0 )
|
2015-07-27 21:45:57 +02:00
|
|
|
bb = m_polys[i][0].BBox();
|
|
|
|
else
|
|
|
|
bb.Merge( m_polys[i][0].BBox() );
|
|
|
|
}
|
|
|
|
|
|
|
|
bb.Inflate( aClearance );
|
|
|
|
return bb;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-05-14 22:01:39 +01:00
|
|
|
const BOX2I SHAPE_POLY_SET::BBoxFromCaches() const
|
|
|
|
{
|
|
|
|
BOX2I bb;
|
|
|
|
|
|
|
|
for( unsigned i = 0; i < m_polys.size(); i++ )
|
|
|
|
{
|
|
|
|
if( i == 0 )
|
2021-09-09 15:35:09 +01:00
|
|
|
bb = *m_polys[i][0].GetCachedBBox();
|
2020-05-14 22:01:39 +01:00
|
|
|
else
|
2021-09-09 15:35:09 +01:00
|
|
|
bb.Merge( *m_polys[i][0].GetCachedBBox() );
|
2020-05-14 22:01:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return bb;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
bool SHAPE_POLY_SET::Collide( const SEG& aSeg, int aClearance, int* aActual,
|
|
|
|
VECTOR2I* aLocation ) const
|
2018-04-19 16:48:20 -07:00
|
|
|
{
|
2020-09-28 23:27:33 +01:00
|
|
|
VECTOR2I nearest;
|
|
|
|
ecoord dist_sq = SquaredDistance( aSeg, aLocation ? &nearest : nullptr );
|
2019-07-16 05:08:29 -07:00
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
|
2019-07-16 05:08:29 -07:00
|
|
|
{
|
2020-09-28 23:27:33 +01:00
|
|
|
if( aLocation )
|
|
|
|
*aLocation = nearest;
|
|
|
|
|
2020-07-02 17:06:09 +01:00
|
|
|
if( aActual )
|
|
|
|
*aActual = sqrt( dist_sq );
|
2019-07-16 05:08:29 -07:00
|
|
|
|
2018-04-19 16:48:20 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
bool SHAPE_POLY_SET::Collide( const VECTOR2I& aP, int aClearance, int* aActual,
|
|
|
|
VECTOR2I* aLocation ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2021-09-26 18:17:50 -04:00
|
|
|
if( IsEmpty() || VertexCount() == 0 )
|
|
|
|
return false;
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
VECTOR2I nearest;
|
|
|
|
ecoord dist_sq = SquaredDistance( aP, aLocation ? &nearest : nullptr );
|
2019-07-16 05:08:29 -07:00
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
if( dist_sq == 0 || dist_sq < SEG::Square( aClearance ) )
|
2019-07-16 05:08:29 -07:00
|
|
|
{
|
2020-09-28 23:27:33 +01:00
|
|
|
if( aLocation )
|
|
|
|
*aLocation = nearest;
|
|
|
|
|
2020-07-02 17:06:09 +01:00
|
|
|
if( aActual )
|
|
|
|
*aActual = sqrt( dist_sq );
|
|
|
|
|
|
|
|
return true;
|
2019-07-16 05:08:29 -07:00
|
|
|
}
|
|
|
|
|
2020-07-02 17:06:09 +01:00
|
|
|
return false;
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-22 00:59:40 +01:00
|
|
|
bool SHAPE_POLY_SET::Collide( const SHAPE* aShape, int aClearance, int* aActual,
|
|
|
|
VECTOR2I* aLocation ) const
|
|
|
|
{
|
|
|
|
// A couple of simple cases are worth trying before we fall back on triangulation.
|
|
|
|
|
|
|
|
if( aShape->Type() == SH_SEGMENT )
|
|
|
|
{
|
|
|
|
const SHAPE_SEGMENT* segment = static_cast<const SHAPE_SEGMENT*>( aShape );
|
|
|
|
int extra = segment->GetWidth() / 2;
|
|
|
|
|
|
|
|
if( Collide( segment->GetSeg(), aClearance + extra, aActual, aLocation ) )
|
|
|
|
{
|
|
|
|
if( aActual )
|
|
|
|
*aActual = std::max( 0, *aActual - extra );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( aShape->Type() == SH_CIRCLE )
|
|
|
|
{
|
|
|
|
const SHAPE_CIRCLE* circle = static_cast<const SHAPE_CIRCLE*>( aShape );
|
|
|
|
int extra = circle->GetRadius();
|
|
|
|
|
|
|
|
if( Collide( circle->GetCenter(), aClearance + extra, aActual, aLocation ) )
|
|
|
|
{
|
|
|
|
if( aActual )
|
|
|
|
*aActual = std::max( 0, *aActual - extra );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const_cast<SHAPE_POLY_SET*>( this )->CacheTriangulation( true );
|
|
|
|
|
|
|
|
int actual = INT_MAX;
|
|
|
|
VECTOR2I location;
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
|
2020-10-22 00:59:40 +01:00
|
|
|
{
|
2021-06-03 00:01:36 +01:00
|
|
|
for( const TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
|
2020-10-22 00:59:40 +01:00
|
|
|
{
|
2021-08-03 14:32:49 +01:00
|
|
|
if( aActual || aLocation )
|
2020-10-22 00:59:40 +01:00
|
|
|
{
|
2021-08-03 14:32:49 +01:00
|
|
|
int triActual;
|
|
|
|
VECTOR2I triLocation;
|
2020-10-22 00:59:40 +01:00
|
|
|
|
2021-08-03 14:32:49 +01:00
|
|
|
if( aShape->Collide( &tri, aClearance, &triActual, &triLocation ) )
|
2020-10-22 00:59:40 +01:00
|
|
|
{
|
2021-08-03 14:32:49 +01:00
|
|
|
if( triActual < actual )
|
|
|
|
{
|
|
|
|
actual = triActual;
|
|
|
|
location = triLocation;
|
|
|
|
}
|
2020-10-22 00:59:40 +01:00
|
|
|
}
|
|
|
|
}
|
2021-08-03 14:32:49 +01:00
|
|
|
else // A much faster version of above
|
|
|
|
{
|
|
|
|
if( aShape->Collide( &tri, aClearance ) )
|
|
|
|
return true;
|
|
|
|
}
|
2020-10-22 00:59:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( actual < INT_MAX )
|
|
|
|
{
|
|
|
|
if( aActual )
|
|
|
|
*aActual = std::max( 0, actual );
|
|
|
|
|
|
|
|
if( aLocation )
|
|
|
|
*aLocation = location;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
void SHAPE_POLY_SET::RemoveAllContours()
|
|
|
|
{
|
|
|
|
m_polys.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
void SHAPE_POLY_SET::RemoveContour( int aContourIdx, int aPolygonIdx )
|
|
|
|
{
|
|
|
|
// Default polygon is the last one
|
|
|
|
if( aPolygonIdx < 0 )
|
|
|
|
aPolygonIdx += m_polys.size();
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
m_polys[aPolygonIdx].erase( m_polys[aPolygonIdx].begin() + aContourIdx );
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int SHAPE_POLY_SET::RemoveNullSegments()
|
|
|
|
{
|
|
|
|
int removed = 0;
|
|
|
|
|
|
|
|
ITERATOR iterator = IterateWithHoles();
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
VECTOR2I contourStart = *iterator;
|
|
|
|
VECTOR2I segmentStart, segmentEnd;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
bool SHAPE_POLY_SET::CollideVertex( const VECTOR2I& aPoint,
|
2020-06-27 12:57:40 +01:00
|
|
|
SHAPE_POLY_SET::VERTEX_INDEX& aClosestVertex,
|
|
|
|
int aClearance ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
// Shows whether there was a collision
|
|
|
|
bool collision = false;
|
|
|
|
|
|
|
|
// Difference vector between each vertex and aPoint.
|
2017-11-23 17:20:27 +01:00
|
|
|
VECTOR2D delta;
|
|
|
|
double distance, clearance;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2021-06-09 19:32:58 +00:00
|
|
|
// Convert clearance to double for precision when comparing distances
|
2017-03-07 13:06:00 +01:00
|
|
|
clearance = aClearance;
|
|
|
|
|
2020-06-27 12:57:40 +01:00
|
|
|
for( CONST_ITERATOR iterator = CIterateWithHoles(); iterator; iterator++ )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
// 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,
|
2020-06-27 12:57:40 +01:00
|
|
|
SHAPE_POLY_SET::VERTEX_INDEX& aClosestVertex,
|
|
|
|
int aClearance ) const
|
2015-07-27 21:45:57 +02:00
|
|
|
{
|
2017-03-07 13:06:00 +01:00
|
|
|
// Shows whether there was a collision
|
|
|
|
bool collision = false;
|
|
|
|
|
2020-06-27 12:57:40 +01:00
|
|
|
for( CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles(); iterator; iterator++ )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2020-06-27 12:57:40 +01:00
|
|
|
const SEG currentSegment = *iterator;
|
2017-03-07 13:06:00 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
|
2021-02-20 13:25:16 +00:00
|
|
|
void SHAPE_POLY_SET::BuildBBoxCaches() const
|
2019-07-05 23:45:57 +01:00
|
|
|
{
|
|
|
|
for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
|
|
|
|
{
|
2021-02-20 13:25:16 +00:00
|
|
|
COutline( polygonIdx ).GenerateBBoxCache();
|
2019-07-05 23:45:57 +01:00
|
|
|
|
|
|
|
for( int holeIdx = 0; holeIdx < HoleCount( polygonIdx ); holeIdx++ )
|
2021-02-20 13:25:16 +00:00
|
|
|
CHole( polygonIdx, holeIdx ).GenerateBBoxCache();
|
2019-07-05 23:45:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-13 17:35:57 +01:00
|
|
|
bool SHAPE_POLY_SET::Contains( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
|
|
|
|
bool aUseBBoxCaches ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2019-07-13 17:35:57 +01:00
|
|
|
if( m_polys.empty() )
|
2015-09-02 08:32:24 +02:00
|
|
|
return false;
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
// If there is a polygon specified, check the condition against that polygon
|
2015-07-27 21:45:57 +02:00
|
|
|
if( aSubpolyIndex >= 0 )
|
2019-07-13 17:35:57 +01:00
|
|
|
return containsSingle( aP, aSubpolyIndex, aAccuracy, aUseBBoxCaches );
|
2015-07-27 21:45:57 +02:00
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
// In any other case, check it against all polygons in the set
|
|
|
|
for( int polygonIdx = 0; polygonIdx < OutlineCount(); polygonIdx++ )
|
2015-07-27 21:45:57 +02:00
|
|
|
{
|
2019-07-13 17:35:57 +01:00
|
|
|
if( containsSingle( aP, polygonIdx, aAccuracy, aUseBBoxCaches ) )
|
2015-07-27 21:45:57 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-23 11:26:44 -07:00
|
|
|
void SHAPE_POLY_SET::SetVertex( int aGlobalIndex, const VECTOR2I& aPos )
|
|
|
|
{
|
|
|
|
VERTEX_INDEX index;
|
|
|
|
|
|
|
|
if( GetRelativeIndices( aGlobalIndex, &index ) )
|
|
|
|
SetVertex( index, aPos );
|
|
|
|
else
|
|
|
|
throw( std::out_of_range( "aGlobalIndex-th vertex does not exist" ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SHAPE_POLY_SET::SetVertex( const VERTEX_INDEX& aIndex, const VECTOR2I& aPos )
|
|
|
|
{
|
|
|
|
m_polys[aIndex.m_polygon][aIndex.m_contour].SetPoint( aIndex.m_vertex, aPos );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-13 17:35:57 +01:00
|
|
|
bool SHAPE_POLY_SET::containsSingle( const VECTOR2I& aP, int aSubpolyIndex, int aAccuracy,
|
|
|
|
bool aUseBBoxCaches ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
// Check that the point is inside the outline
|
2019-07-13 17:35:57 +01:00
|
|
|
if( m_polys[aSubpolyIndex][0].PointInside( aP, aAccuracy ) )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2019-07-13 17:35:57 +01:00
|
|
|
// Check that the point is not in any of the holes
|
|
|
|
for( int holeIdx = 0; holeIdx < HoleCount( aSubpolyIndex ); holeIdx++ )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2019-07-13 17:35:57 +01:00
|
|
|
const SHAPE_LINE_CHAIN& hole = CHole( aSubpolyIndex, holeIdx );
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2019-07-13 17:35:57 +01:00
|
|
|
// If the point is inside a hole it is outside of the polygon. Do not use aAccuracy
|
|
|
|
// here as it's meaning would be inverted.
|
|
|
|
if( hole.PointInside( aP, 1, aUseBBoxCaches ) )
|
|
|
|
return false;
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SHAPE_POLY_SET::Move( const VECTOR2I& aVector )
|
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
for( POLYGON& poly : m_polys )
|
2015-07-27 21:45:57 +02:00
|
|
|
{
|
2017-11-23 17:20:27 +01:00
|
|
|
for( SHAPE_LINE_CHAIN& path : poly )
|
2015-07-27 21:45:57 +02:00
|
|
|
path.Move( aVector );
|
|
|
|
}
|
2020-06-17 19:29:21 -07:00
|
|
|
|
2020-12-12 15:14:41 +00:00
|
|
|
for( std::unique_ptr<TRIANGULATED_POLYGON>& tri : m_triangulatedPolys )
|
2020-06-17 19:29:21 -07:00
|
|
|
tri->Move( aVector );
|
|
|
|
|
|
|
|
m_hash = checksum();
|
2015-07-27 21:45:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-23 11:26:44 -07:00
|
|
|
void SHAPE_POLY_SET::Mirror( bool aX, bool aY, const VECTOR2I& aRef )
|
|
|
|
{
|
|
|
|
for( POLYGON& poly : m_polys )
|
|
|
|
{
|
|
|
|
for( SHAPE_LINE_CHAIN& path : poly )
|
|
|
|
path.Mirror( aX, aY, aRef );
|
|
|
|
}
|
2020-12-20 10:45:24 -05:00
|
|
|
|
|
|
|
if( m_triangulationValid )
|
|
|
|
CacheTriangulation();
|
2019-03-23 11:26:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-01-20 20:54:22 +00:00
|
|
|
void SHAPE_POLY_SET::Rotate( const EDA_ANGLE& aAngle, const VECTOR2I& aCenter )
|
2017-12-22 10:54:47 +01:00
|
|
|
{
|
|
|
|
for( POLYGON& poly : m_polys )
|
|
|
|
{
|
|
|
|
for( SHAPE_LINE_CHAIN& path : poly )
|
|
|
|
path.Rotate( aAngle, aCenter );
|
|
|
|
}
|
2020-06-30 13:32:48 -04:00
|
|
|
|
2020-09-08 11:17:50 -07:00
|
|
|
// Don't re-cache if the triangulation is already invalid
|
|
|
|
if( m_triangulationValid )
|
|
|
|
CacheTriangulation();
|
2017-12-22 10:54:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-27 21:45:57 +02:00
|
|
|
int SHAPE_POLY_SET::TotalVertices() const
|
|
|
|
{
|
|
|
|
int c = 0;
|
|
|
|
|
2016-06-29 16:07:55 -04:00
|
|
|
for( const POLYGON& poly : m_polys )
|
2015-07-27 21:45:57 +02:00
|
|
|
{
|
2016-06-29 16:07:55 -04:00
|
|
|
for( const SHAPE_LINE_CHAIN& path : poly )
|
2015-07-27 21:45:57 +02:00
|
|
|
c += path.PointCount();
|
|
|
|
}
|
|
|
|
|
|
|
|
return c;
|
2015-06-12 17:12:02 +02:00
|
|
|
}
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
|
2020-08-12 19:42:40 +01:00
|
|
|
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::ChamferPolygon( unsigned int aDistance, int aIndex )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2020-08-12 19:42:40 +01:00
|
|
|
return chamferFilletPolygon( CHAMFERED, aDistance, aIndex, 0 );
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-13 23:34:09 +01:00
|
|
|
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::FilletPolygon( unsigned int aRadius, int aErrorMax,
|
2020-08-12 19:42:40 +01:00
|
|
|
int aIndex )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2020-08-12 19:42:40 +01:00
|
|
|
return chamferFilletPolygon( FILLETED, aRadius, aIndex, aErrorMax );
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
SEG::ecoord SHAPE_POLY_SET::SquaredDistanceToPolygon( VECTOR2I aPoint, int aPolygonIndex,
|
|
|
|
VECTOR2I* aNearest ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2019-07-13 17:35:57 +01:00
|
|
|
// 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).
|
|
|
|
// Use an accuracy of "1" to say that we don't care if it's exactly on the edge or not.
|
|
|
|
if( containsSingle( aPoint, aPolygonIndex, 1 ) )
|
2020-09-28 23:27:33 +01:00
|
|
|
{
|
|
|
|
if( aNearest )
|
|
|
|
*aNearest = aPoint;
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
return 0;
|
2020-09-28 23:27:33 +01:00
|
|
|
}
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2020-06-21 15:58:44 +01:00
|
|
|
CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2020-09-17 17:32:42 +01:00
|
|
|
SEG::ecoord minDistance = (*iterator).SquaredDistance( aPoint );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
for( iterator++; iterator && minDistance > 0; iterator++ )
|
|
|
|
{
|
2020-09-17 17:32:42 +01:00
|
|
|
SEG::ecoord currentDistance = (*iterator).SquaredDistance( aPoint );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
if( currentDistance < minDistance )
|
2020-09-28 23:27:33 +01:00
|
|
|
{
|
|
|
|
if( aNearest )
|
|
|
|
*aNearest = (*iterator).NearestPoint( aPoint );
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
minDistance = currentDistance;
|
2020-09-28 23:27:33 +01:00
|
|
|
}
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return minDistance;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
SEG::ecoord SHAPE_POLY_SET::SquaredDistanceToPolygon( const SEG& aSegment, int aPolygonIndex,
|
|
|
|
VECTOR2I* aNearest ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2021-04-04 20:12:45 -04:00
|
|
|
// Check if the segment is fully-contained. If so, its midpoint is a good-enough nearest point.
|
|
|
|
if( containsSingle( aSegment.A, aPolygonIndex, 1 ) &&
|
|
|
|
containsSingle( aSegment.B, aPolygonIndex, 1 ) )
|
2020-09-28 23:27:33 +01:00
|
|
|
{
|
|
|
|
if( aNearest )
|
|
|
|
*aNearest = ( aSegment.A + aSegment.B ) / 2;
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
return 0;
|
2020-09-28 23:27:33 +01:00
|
|
|
}
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2020-06-21 15:58:44 +01:00
|
|
|
CONST_SEGMENT_ITERATOR iterator = CIterateSegmentsWithHoles( aPolygonIndex );
|
2020-09-17 17:32:42 +01:00
|
|
|
SEG::ecoord minDistance = (*iterator).SquaredDistance( aSegment );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2021-04-04 19:11:54 -04:00
|
|
|
if( aNearest && minDistance == 0 )
|
|
|
|
*aNearest = ( *iterator ).NearestPoint( aSegment );
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
for( iterator++; iterator && minDistance > 0; iterator++ )
|
|
|
|
{
|
2020-09-17 17:32:42 +01:00
|
|
|
SEG::ecoord currentDistance = (*iterator).SquaredDistance( aSegment );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
if( currentDistance < minDistance )
|
2020-09-28 23:27:33 +01:00
|
|
|
{
|
|
|
|
if( aNearest )
|
|
|
|
*aNearest = (*iterator).NearestPoint( aSegment );
|
|
|
|
|
2017-03-07 13:06:00 +01:00
|
|
|
minDistance = currentDistance;
|
2020-09-28 23:27:33 +01:00
|
|
|
}
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return the maximum of minDistance and zero
|
|
|
|
return minDistance < 0 ? 0 : minDistance;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
SEG::ecoord SHAPE_POLY_SET::SquaredDistance( VECTOR2I aPoint, VECTOR2I* aNearest ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2020-09-28 23:27:33 +01:00
|
|
|
SEG::ecoord currentDistance_sq;
|
|
|
|
SEG::ecoord minDistance_sq = VECTOR2I::ECOORD_MAX;
|
2020-12-12 15:14:41 +00:00
|
|
|
VECTOR2I nearest;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// Iterate through all the polygons and get the minimum distance.
|
2020-09-28 23:27:33 +01:00
|
|
|
for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2020-09-28 23:27:33 +01:00
|
|
|
currentDistance_sq = SquaredDistanceToPolygon( aPoint, polygonIdx,
|
|
|
|
aNearest ? &nearest : nullptr );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
if( currentDistance_sq < minDistance_sq )
|
|
|
|
{
|
|
|
|
if( aNearest )
|
|
|
|
*aNearest = nearest;
|
|
|
|
|
|
|
|
minDistance_sq = currentDistance_sq;
|
|
|
|
}
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
return minDistance_sq;
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
SEG::ecoord SHAPE_POLY_SET::SquaredDistance( const SEG& aSegment, VECTOR2I* aNearest ) const
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2020-09-28 23:27:33 +01:00
|
|
|
SEG::ecoord currentDistance_sq;
|
|
|
|
SEG::ecoord minDistance_sq = VECTOR2I::ECOORD_MAX;
|
2020-12-12 15:14:41 +00:00
|
|
|
VECTOR2I nearest;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// Iterate through all the polygons and get the minimum distance.
|
2020-09-28 23:27:33 +01:00
|
|
|
for( unsigned int polygonIdx = 0; polygonIdx < m_polys.size(); polygonIdx++ )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2020-09-28 23:27:33 +01:00
|
|
|
currentDistance_sq = SquaredDistanceToPolygon( aSegment, polygonIdx,
|
2020-12-12 15:14:41 +00:00
|
|
|
aNearest ? &nearest : nullptr );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
if( currentDistance_sq < minDistance_sq )
|
|
|
|
{
|
|
|
|
if( aNearest )
|
|
|
|
*aNearest = nearest;
|
|
|
|
|
|
|
|
minDistance_sq = currentDistance_sq;
|
|
|
|
}
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
2020-09-28 23:27:33 +01:00
|
|
|
return minDistance_sq;
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-08-12 19:42:40 +01:00
|
|
|
SHAPE_POLY_SET SHAPE_POLY_SET::Chamfer( int aDistance )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
SHAPE_POLY_SET chamfered;
|
|
|
|
|
2019-07-13 23:34:09 +01:00
|
|
|
for( unsigned int idx = 0; idx < m_polys.size(); idx++ )
|
2020-08-12 19:42:40 +01:00
|
|
|
chamfered.m_polys.push_back( ChamferPolygon( aDistance, idx ) );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
return chamfered;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-08-12 19:42:40 +01:00
|
|
|
SHAPE_POLY_SET SHAPE_POLY_SET::Fillet( int aRadius, int aErrorMax )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
SHAPE_POLY_SET filleted;
|
|
|
|
|
2019-07-13 23:34:09 +01:00
|
|
|
for( size_t idx = 0; idx < m_polys.size(); idx++ )
|
2020-08-12 19:42:40 +01:00
|
|
|
filleted.m_polys.push_back( FilletPolygon( aRadius, aErrorMax, idx ) );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
return filleted;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SHAPE_POLY_SET::POLYGON SHAPE_POLY_SET::chamferFilletPolygon( CORNER_MODE aMode,
|
2021-07-18 10:06:48 -04:00
|
|
|
unsigned int aDistance,
|
|
|
|
int aIndex, int aErrorMax )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
// 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.
|
2017-11-23 17:20:27 +01:00
|
|
|
for( SHAPE_LINE_CHAIN& currContour : currentPoly )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
// 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
|
2019-03-23 11:26:44 -07:00
|
|
|
int x1 = currContour.CPoint( currVertex ).x;
|
|
|
|
int y1 = currContour.CPoint( currVertex ).y;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// 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
|
2019-03-23 11:26:44 -07:00
|
|
|
double xa = currContour.CPoint( prevVertex ).x - x1;
|
|
|
|
double ya = currContour.CPoint( prevVertex ).y - y1;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// Next vertex computation
|
2019-03-23 11:26:44 -07:00
|
|
|
double xb = currContour.CPoint( nextVertex ).x - x1;
|
|
|
|
double yb = currContour.CPoint( nextVertex ).y - y1;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// Compute the new distances
|
2017-11-23 17:20:27 +01:00
|
|
|
double lena = hypot( xa, ya );
|
|
|
|
double lenb = hypot( xb, yb );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2020-01-07 23:27:29 +00:00
|
|
|
int nx1 = KiROUND( distance * xa / lena );
|
|
|
|
int ny1 = KiROUND( distance * ya / lena );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
newContour.Append( x1 + nx1, y1 + ny1 );
|
|
|
|
|
2020-01-07 23:27:29 +00:00
|
|
|
int nx2 = KiROUND( distance * xb / lenb );
|
|
|
|
int ny2 = KiROUND( distance * yb / lenb );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
newContour.Append( x1 + nx2, y1 + ny2 );
|
|
|
|
}
|
2017-11-23 17:20:27 +01:00
|
|
|
else // CORNER_MODE = FILLETED
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
|
|
|
double cosine = ( xa * xb + ya * yb ) / ( lena * lenb );
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
double radius = aDistance;
|
|
|
|
double denom = sqrt( 2.0 / ( 1 + cosine ) - 1 );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// 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)
|
2017-11-23 17:20:27 +01:00
|
|
|
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;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// Calculate arc start and end vectors
|
|
|
|
k = radius / sqrt( 2 / ( 1 + cosine ) - 1 );
|
2017-11-23 17:20:27 +01:00
|
|
|
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;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// 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 );
|
2022-01-14 15:34:41 +00:00
|
|
|
int segments = GetArcToSegmentCount( radius, aErrorMax,
|
|
|
|
EDA_ANGLE( arcAngle, RADIANS_T ) );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2022-01-14 15:34:41 +00:00
|
|
|
double deltaAngle = arcAngle / segments;
|
|
|
|
double startAngle = atan2( -ys, xs );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// Flip arc for inner corners
|
|
|
|
if( xa * yb - ya * xb <= 0 )
|
|
|
|
deltaAngle *= -1;
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
double nx = xc + xs;
|
|
|
|
double ny = yc + ys;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2020-01-07 23:27:29 +00:00
|
|
|
newContour.Append( KiROUND( nx ), KiROUND( ny ) );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// Store the previous added corner to make a sanity check
|
2020-01-07 23:27:29 +00:00
|
|
|
int prevX = KiROUND( nx );
|
|
|
|
int prevY = KiROUND( ny );
|
2017-03-07 13:06:00 +01:00
|
|
|
|
2018-03-23 15:07:38 +01:00
|
|
|
for( int j = 0; j < segments; j++ )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2018-02-19 10:03:56 +01:00
|
|
|
nx = xc + cos( startAngle + ( j + 1 ) * deltaAngle ) * radius;
|
|
|
|
ny = yc - sin( startAngle + ( j + 1 ) * deltaAngle ) * radius;
|
2017-03-07 13:06:00 +01:00
|
|
|
|
|
|
|
// Sanity check: the rounding can produce repeated corners; do not add them.
|
2020-01-07 23:27:29 +00:00
|
|
|
if( KiROUND( nx ) != prevX || KiROUND( ny ) != prevY )
|
2017-03-07 13:06:00 +01:00
|
|
|
{
|
2020-01-07 23:27:29 +00:00
|
|
|
newContour.Append( KiROUND( nx ), KiROUND( ny ) );
|
|
|
|
prevX = KiROUND( nx );
|
|
|
|
prevY = KiROUND( ny );
|
2017-03-07 13:06:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the current contour and add it the new polygon
|
|
|
|
newContour.SetClosed( true );
|
|
|
|
newPoly.push_back( newContour );
|
|
|
|
}
|
|
|
|
|
|
|
|
return newPoly;
|
|
|
|
}
|
2017-11-23 17:20:27 +01:00
|
|
|
|
|
|
|
|
2018-02-19 10:03:56 +01:00
|
|
|
SHAPE_POLY_SET &SHAPE_POLY_SET::operator=( const SHAPE_POLY_SET& aOther )
|
2017-12-08 12:20:02 +01:00
|
|
|
{
|
|
|
|
static_cast<SHAPE&>(*this) = aOther;
|
|
|
|
m_polys = aOther.m_polys;
|
|
|
|
m_triangulatedPolys.clear();
|
2020-06-17 19:29:21 -07:00
|
|
|
m_triangulationValid = false;
|
|
|
|
|
|
|
|
if( aOther.IsTriangulationUpToDate() )
|
|
|
|
{
|
|
|
|
for( unsigned i = 0; i < aOther.TriangulatedPolyCount(); i++ )
|
2020-12-12 15:14:41 +00:00
|
|
|
{
|
|
|
|
const TRIANGULATED_POLYGON* poly = aOther.TriangulatedPolygon( i );
|
|
|
|
m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>( *poly ) );
|
|
|
|
}
|
2020-06-17 19:29:21 -07:00
|
|
|
|
|
|
|
m_hash = aOther.GetHash();
|
|
|
|
m_triangulationValid = true;
|
|
|
|
}
|
|
|
|
|
2017-12-08 12:20:02 +01:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2021-07-18 10:06:48 -04:00
|
|
|
|
2018-04-06 11:24:23 +01:00
|
|
|
MD5_HASH SHAPE_POLY_SET::GetHash() const
|
|
|
|
{
|
|
|
|
if( !m_hash.IsValid() )
|
|
|
|
return checksum();
|
|
|
|
|
|
|
|
return m_hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
bool SHAPE_POLY_SET::IsTriangulationUpToDate() const
|
|
|
|
{
|
|
|
|
if( !m_triangulationValid )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if( !m_hash.IsValid() )
|
|
|
|
return false;
|
|
|
|
|
2020-12-12 15:14:41 +00:00
|
|
|
MD5_HASH hash = checksum();
|
2017-11-23 17:20:27 +01:00
|
|
|
|
|
|
|
return hash == m_hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-12 15:14:41 +00:00
|
|
|
static void partitionPolyIntoRegularCellGrid( const SHAPE_POLY_SET& aPoly, int aSize,
|
|
|
|
SHAPE_POLY_SET& aOut )
|
2020-09-05 01:09:34 +02:00
|
|
|
{
|
|
|
|
BOX2I bb = aPoly.BBox();
|
|
|
|
|
|
|
|
double w = bb.GetWidth();
|
|
|
|
double h = bb.GetHeight();
|
|
|
|
|
|
|
|
if( w == 0.0 || h == 0.0 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
int n_cells_x, n_cells_y;
|
|
|
|
|
|
|
|
if( w > h )
|
|
|
|
{
|
2020-09-10 09:28:52 -07:00
|
|
|
n_cells_x = w / aSize;
|
|
|
|
n_cells_y = floor( h / w * n_cells_x ) + 1;
|
2020-09-05 01:09:34 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-09-10 09:28:52 -07:00
|
|
|
n_cells_y = h / aSize;
|
|
|
|
n_cells_x = floor( w / h * n_cells_y ) + 1;
|
2020-09-05 01:09:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
SHAPE_POLY_SET ps1( aPoly ), ps2( aPoly ), maskSetOdd, maskSetEven;
|
|
|
|
|
|
|
|
for( int yy = 0; yy < n_cells_y; yy++ )
|
2020-12-12 15:14:41 +00:00
|
|
|
{
|
2020-09-05 01:09:34 +02:00
|
|
|
for( int xx = 0; xx < n_cells_x; xx++ )
|
|
|
|
{
|
|
|
|
VECTOR2I p;
|
|
|
|
|
|
|
|
p.x = bb.GetX() + w * xx / n_cells_x;
|
|
|
|
p.y = bb.GetY() + h * yy / n_cells_y;
|
|
|
|
|
|
|
|
VECTOR2I p2;
|
|
|
|
|
|
|
|
p2.x = bb.GetX() + w * ( xx + 1 ) / n_cells_x;
|
|
|
|
p2.y = bb.GetY() + h * ( yy + 1 ) / n_cells_y;
|
|
|
|
|
|
|
|
|
|
|
|
SHAPE_LINE_CHAIN mask;
|
|
|
|
mask.Append( VECTOR2I( p.x, p.y ) );
|
|
|
|
mask.Append( VECTOR2I( p2.x, p.y ) );
|
|
|
|
mask.Append( VECTOR2I( p2.x, p2.y ) );
|
|
|
|
mask.Append( VECTOR2I( p.x, p2.y ) );
|
|
|
|
mask.SetClosed( true );
|
|
|
|
|
|
|
|
if( ( xx ^ yy ) & 1 )
|
|
|
|
maskSetOdd.AddOutline( mask );
|
|
|
|
else
|
|
|
|
maskSetEven.AddOutline( mask );
|
|
|
|
}
|
2020-12-12 15:14:41 +00:00
|
|
|
}
|
2020-09-05 01:09:34 +02:00
|
|
|
|
|
|
|
ps1.BooleanIntersection( maskSetOdd, SHAPE_POLY_SET::PM_FAST );
|
|
|
|
ps2.BooleanIntersection( maskSetEven, SHAPE_POLY_SET::PM_FAST );
|
|
|
|
ps1.Fracture( SHAPE_POLY_SET::PM_FAST );
|
|
|
|
ps2.Fracture( SHAPE_POLY_SET::PM_FAST );
|
|
|
|
|
|
|
|
aOut = ps1;
|
2020-12-12 15:14:41 +00:00
|
|
|
|
2020-09-05 01:09:34 +02:00
|
|
|
for( int i = 0; i < ps2.OutlineCount(); i++ )
|
|
|
|
aOut.AddOutline( ps2.COutline( i ) );
|
2020-09-11 08:44:26 -07:00
|
|
|
|
|
|
|
if( !aOut.OutlineCount() )
|
|
|
|
aOut = aPoly;
|
2020-09-05 01:09:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-09-10 09:28:52 -07:00
|
|
|
void SHAPE_POLY_SET::CacheTriangulation( bool aPartition )
|
2017-11-23 17:20:27 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2020-09-05 01:09:34 +02:00
|
|
|
SHAPE_POLY_SET tmpSet;
|
2020-09-08 11:17:50 -07:00
|
|
|
|
2020-09-10 09:28:52 -07:00
|
|
|
if( aPartition )
|
2020-12-12 15:14:41 +00:00
|
|
|
{
|
2021-07-18 10:06:48 -04:00
|
|
|
// This partitions into regularly-sized grids (1cm in Pcbnew)
|
2021-07-04 12:09:59 -04:00
|
|
|
SHAPE_POLY_SET flattened( *this );
|
|
|
|
flattened.ClearArcs();
|
|
|
|
partitionPolyIntoRegularCellGrid( flattened, 1e7, tmpSet );
|
2020-12-12 15:14:41 +00:00
|
|
|
}
|
2020-09-10 09:28:52 -07:00
|
|
|
else
|
2020-09-13 17:55:59 +02:00
|
|
|
{
|
2020-09-10 09:28:52 -07:00
|
|
|
tmpSet = *this;
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2020-09-13 17:55:59 +02:00
|
|
|
if( tmpSet.HasHoles() )
|
|
|
|
tmpSet.Fracture( PM_FAST );
|
|
|
|
}
|
|
|
|
|
2017-11-23 17:20:27 +01:00
|
|
|
m_triangulatedPolys.clear();
|
2021-01-29 16:32:48 -08:00
|
|
|
m_triangulationValid = false;
|
2017-11-23 17:20:27 +01:00
|
|
|
|
2019-01-18 07:13:50 -08:00
|
|
|
while( tmpSet.OutlineCount() > 0 )
|
2017-11-23 17:20:27 +01:00
|
|
|
{
|
2021-10-12 12:13:14 -07:00
|
|
|
|
|
|
|
if( !m_triangulatedPolys.empty() && m_triangulatedPolys.back()->GetTriangleCount() == 0 )
|
|
|
|
m_triangulatedPolys.erase( m_triangulatedPolys.end() - 1 );
|
|
|
|
|
2017-12-08 12:20:02 +01:00
|
|
|
m_triangulatedPolys.push_back( std::make_unique<TRIANGULATED_POLYGON>() );
|
2018-07-12 21:52:21 -07:00
|
|
|
PolygonTriangulation tess( *m_triangulatedPolys.back() );
|
|
|
|
|
2021-07-18 10:06:48 -04:00
|
|
|
// If the tessellation fails, we re-fracture the polygon, which will
|
2018-12-17 06:51:12 -07:00
|
|
|
// first simplify the system before fracturing and removing the holes
|
2019-01-18 07:13:50 -08:00
|
|
|
// This may result in multiple, disjoint polygons.
|
|
|
|
if( !tess.TesselatePolygon( tmpSet.Polygon( 0 ).front() ) )
|
2018-12-17 06:51:12 -07:00
|
|
|
{
|
|
|
|
tmpSet.Fracture( PM_FAST );
|
2019-01-18 07:13:50 -08:00
|
|
|
m_triangulationValid = false;
|
|
|
|
continue;
|
2018-12-17 06:51:12 -07:00
|
|
|
}
|
2019-01-18 07:13:50 -08:00
|
|
|
|
|
|
|
tmpSet.DeletePolygon( 0 );
|
|
|
|
m_triangulationValid = true;
|
2017-11-23 17:20:27 +01:00
|
|
|
}
|
|
|
|
|
2018-12-18 06:17:01 -07:00
|
|
|
if( m_triangulationValid )
|
|
|
|
m_hash = checksum();
|
2017-11-23 17:20:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MD5_HASH SHAPE_POLY_SET::checksum() const
|
|
|
|
{
|
|
|
|
MD5_HASH hash;
|
|
|
|
|
|
|
|
hash.Hash( m_polys.size() );
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
for( const POLYGON& outline : m_polys )
|
2017-11-23 17:20:27 +01:00
|
|
|
{
|
|
|
|
hash.Hash( outline.size() );
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
for( const SHAPE_LINE_CHAIN& lc : outline )
|
2017-11-23 17:20:27 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2017-12-14 00:33:20 +01:00
|
|
|
|
2018-02-19 10:03:56 +01:00
|
|
|
|
2017-12-14 00:33:20 +01:00
|
|
|
bool SHAPE_POLY_SET::HasTouchingHoles() const
|
|
|
|
{
|
|
|
|
for( int i = 0; i < OutlineCount(); i++ )
|
|
|
|
{
|
|
|
|
if( hasTouchingHoles( CPolygon( i ) ) )
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-02-19 10:03:56 +01:00
|
|
|
|
2017-12-14 00:33:20 +01:00
|
|
|
bool SHAPE_POLY_SET::hasTouchingHoles( const POLYGON& aPoly ) const
|
|
|
|
{
|
2021-06-03 00:01:36 +01:00
|
|
|
std::set<long long> ptHashes;
|
2017-12-14 00:33:20 +01:00
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
for( const SHAPE_LINE_CHAIN& lc : aPoly )
|
2017-12-14 00:33:20 +01:00
|
|
|
{
|
2018-08-07 12:13:23 +01:00
|
|
|
for( const VECTOR2I& pt : lc.CPoints() )
|
2017-12-14 00:33:20 +01:00
|
|
|
{
|
2018-08-07 12:13:23 +01:00
|
|
|
const long long ptHash = (long long) pt.x << 32 | pt.y;
|
2017-12-14 00:33:20 +01:00
|
|
|
|
2018-08-07 12:13:23 +01:00
|
|
|
if( ptHashes.count( ptHash ) > 0 )
|
2017-12-14 00:33:20 +01:00
|
|
|
return true;
|
|
|
|
|
2018-08-07 12:13:23 +01:00
|
|
|
ptHashes.insert( ptHash );
|
2017-12-14 00:33:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2020-09-18 16:22:30 +02:00
|
|
|
|
2020-10-08 15:22:02 +02:00
|
|
|
|
2020-09-18 16:22:30 +02:00
|
|
|
bool SHAPE_POLY_SET::HasIndexableSubshapes() const
|
|
|
|
{
|
|
|
|
return IsTriangulationUpToDate();
|
|
|
|
}
|
|
|
|
|
2020-10-08 15:22:02 +02:00
|
|
|
|
2020-09-18 16:22:30 +02:00
|
|
|
size_t SHAPE_POLY_SET::GetIndexableSubshapeCount() const
|
|
|
|
{
|
|
|
|
size_t n = 0;
|
2020-10-22 00:59:40 +01:00
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
for( const std::unique_ptr<TRIANGULATED_POLYGON>& t : m_triangulatedPolys )
|
2020-09-18 16:22:30 +02:00
|
|
|
n += t->GetTriangleCount();
|
2020-10-22 00:59:40 +01:00
|
|
|
|
2020-09-18 16:22:30 +02:00
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
2020-10-08 15:22:02 +02:00
|
|
|
|
2020-09-18 16:22:30 +02:00
|
|
|
void SHAPE_POLY_SET:: GetIndexableSubshapes( std::vector<SHAPE*>& aSubshapes )
|
|
|
|
{
|
|
|
|
aSubshapes.reserve( GetIndexableSubshapeCount() );
|
|
|
|
|
2021-06-03 00:01:36 +01:00
|
|
|
for( const std::unique_ptr<TRIANGULATED_POLYGON>& tpoly : m_triangulatedPolys )
|
2020-09-18 16:22:30 +02:00
|
|
|
{
|
2021-06-03 00:01:36 +01:00
|
|
|
for( TRIANGULATED_POLYGON::TRI& tri : tpoly->Triangles() )
|
2020-09-18 16:22:30 +02:00
|
|
|
aSubshapes.push_back( &tri );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-08 15:22:02 +02:00
|
|
|
|
|
|
|
const BOX2I SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRI::BBox( int aClearance ) const
|
|
|
|
{
|
|
|
|
BOX2I bbox( parent->m_vertices[a] );
|
|
|
|
bbox.Merge( parent->m_vertices[b] );
|
|
|
|
bbox.Merge( parent->m_vertices[c] );
|
|
|
|
|
|
|
|
if( aClearance != 0 )
|
|
|
|
bbox.Inflate( aClearance );
|
|
|
|
|
|
|
|
return bbox;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void SHAPE_POLY_SET::TRIANGULATED_POLYGON::AddTriangle( int a, int b, int c )
|
|
|
|
{
|
|
|
|
m_triangles.emplace_back( a, b, c, this );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRIANGULATED_POLYGON( const TRIANGULATED_POLYGON& aOther )
|
|
|
|
{
|
|
|
|
m_vertices = aOther.m_vertices;
|
|
|
|
m_triangles = aOther.m_triangles;
|
|
|
|
|
|
|
|
for( TRI& tri : m_triangles )
|
|
|
|
tri.parent = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SHAPE_POLY_SET::TRIANGULATED_POLYGON& SHAPE_POLY_SET::TRIANGULATED_POLYGON::operator=( const TRIANGULATED_POLYGON& aOther )
|
|
|
|
{
|
|
|
|
m_vertices = aOther.m_vertices;
|
|
|
|
m_triangles = aOther.m_triangles;
|
|
|
|
|
|
|
|
for( TRI& tri : m_triangles )
|
|
|
|
tri.parent = this;
|
|
|
|
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SHAPE_POLY_SET::TRIANGULATED_POLYGON::TRIANGULATED_POLYGON()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SHAPE_POLY_SET::TRIANGULATED_POLYGON::~TRIANGULATED_POLYGON()
|
|
|
|
{
|
|
|
|
}
|
2021-12-30 03:45:53 +01:00
|
|
|
|
|
|
|
|
|
|
|
const SHAPE_POLY_SET
|
|
|
|
SHAPE_POLY_SET::BuildPolysetFromOrientedPaths( const std::vector<SHAPE_LINE_CHAIN>& aPaths,
|
|
|
|
bool aReverseOrientation, bool aEvenOdd )
|
|
|
|
{
|
|
|
|
ClipperLib::Clipper clipper;
|
|
|
|
ClipperLib::PolyTree tree;
|
|
|
|
|
|
|
|
// fixme: do we need aReverseOrientation?
|
|
|
|
|
|
|
|
for( const SHAPE_LINE_CHAIN& path : aPaths )
|
|
|
|
{
|
|
|
|
ClipperLib::Path lc;
|
|
|
|
|
|
|
|
for( int i = 0; i < path.PointCount(); i++ )
|
|
|
|
{
|
|
|
|
lc.emplace_back( path.CPoint( i ).x, path.CPoint( i ).y );
|
|
|
|
}
|
|
|
|
|
|
|
|
clipper.AddPath( lc, ClipperLib::ptSubject, true );
|
|
|
|
}
|
|
|
|
|
|
|
|
clipper.StrictlySimple( true );
|
|
|
|
clipper.Execute( ClipperLib::ctUnion, tree,
|
|
|
|
aEvenOdd ? ClipperLib::pftEvenOdd : ClipperLib::pftNonZero,
|
|
|
|
ClipperLib::pftNonZero );
|
|
|
|
SHAPE_POLY_SET result;
|
|
|
|
|
|
|
|
for( ClipperLib::PolyNode* n = tree.GetFirst(); n; n = n->GetNext() )
|
|
|
|
{
|
|
|
|
if( !n->IsHole() )
|
|
|
|
{
|
|
|
|
int outl = result.NewOutline();
|
|
|
|
|
|
|
|
for( unsigned int i = 0; i < n->Contour.size(); i++ )
|
|
|
|
result.Outline( outl ).Append( n->Contour[i].X, n->Contour[i].Y );
|
|
|
|
|
|
|
|
for( unsigned int i = 0; i < n->Childs.size(); i++ )
|
|
|
|
{
|
|
|
|
int outh = result.NewHole( outl );
|
|
|
|
for( unsigned int j = 0; j < n->Childs[i]->Contour.size(); j++ )
|
|
|
|
{
|
|
|
|
result.Hole( outl, outh )
|
|
|
|
.Append( n->Childs[i]->Contour[j].X, n->Childs[i]->Contour[j].Y );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|