Update triangulation
This commit is contained in:
parent
db12f2af49
commit
4f03bb2fb6
|
@ -107,6 +107,7 @@ static const wxChar PcbSelectionVisibilityRatio[] = wxT( "PcbSelectionVisibility
|
|||
static const wxChar MinimumSegmentLength[] = wxT( "MinimumSegmentLength" );
|
||||
static const wxChar OcePluginLinearDeflection[] = wxT( "OcePluginLinearDeflection" );
|
||||
static const wxChar OcePluginAngularDeflection[] = wxT( "OcePluginAngularDeflection" );
|
||||
static const wxChar TriangulateSimplificationLevel[] = wxT( "TriangulateSimplificationLevel" );
|
||||
} // namespace KEYS
|
||||
|
||||
|
||||
|
@ -255,6 +256,8 @@ ADVANCED_CFG::ADVANCED_CFG()
|
|||
m_OcePluginLinearDeflection = 0.14;
|
||||
m_OcePluginAngularDeflection = 30;
|
||||
|
||||
m_TriangulateSimplificationLevel = 50;
|
||||
|
||||
loadFromConfigFile();
|
||||
}
|
||||
|
||||
|
@ -462,6 +465,10 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
|
|||
&m_OcePluginAngularDeflection,
|
||||
m_OcePluginAngularDeflection, 0.01, 360.0 ) );
|
||||
|
||||
configParams.push_back( new PARAM_CFG_INT( true, AC_KEYS::TriangulateSimplificationLevel,
|
||||
&m_TriangulateSimplificationLevel,
|
||||
m_TriangulateSimplificationLevel, 0, 1000 ) );
|
||||
|
||||
// Special case for trace mask setting...we just grab them and set them immediately
|
||||
// Because we even use wxLogTrace inside of advanced config
|
||||
wxString traceMasks;
|
||||
|
|
|
@ -545,6 +545,16 @@ public:
|
|||
*/
|
||||
double m_OcePluginAngularDeflection;
|
||||
|
||||
/**
|
||||
* The number of internal units that will be allowed to deflect from the base
|
||||
* segment when creating a new segment.
|
||||
*
|
||||
* Setting name: "TriangulateSimplificationLevel"
|
||||
* Valid values: 0 to 1000
|
||||
* Default value: 50
|
||||
*/
|
||||
int m_TriangulateSimplificationLevel;
|
||||
|
||||
///@}
|
||||
|
||||
|
||||
|
|
|
@ -56,6 +56,8 @@
|
|||
#include <math/box2.h>
|
||||
#include <math/vector2d.h>
|
||||
|
||||
#include <wx/log.h>
|
||||
|
||||
#define TRIANGULATE_TRACE "triangulate"
|
||||
class POLYGON_TRIANGULATION
|
||||
{
|
||||
|
@ -258,6 +260,23 @@ private:
|
|||
&& ( b.x - x ) * ( c.y - y ) - ( c.x - x ) * ( b.y - y ) >= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signed area of the polygon connected to the current vertex
|
||||
*/
|
||||
double area() const
|
||||
{
|
||||
const VERTEX* p = this;
|
||||
double a = 0.0;
|
||||
|
||||
do
|
||||
{
|
||||
a += ( p->x * p->next->y - p->next->x * p->y );
|
||||
p = p->next;
|
||||
} while( p != this );
|
||||
|
||||
return a / 2;
|
||||
}
|
||||
|
||||
const size_t i;
|
||||
const double x;
|
||||
const double y;
|
||||
|
@ -298,6 +317,35 @@ private:
|
|||
return x | ( y << 1 );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Outputs a list of vertices that have not yet been triangulated.
|
||||
*/
|
||||
void logRemaining()
|
||||
{
|
||||
std::set<VERTEX*> seen;
|
||||
|
||||
for( VERTEX& p : m_vertices )
|
||||
{
|
||||
if( !p.next || p.next == &p || seen.find( &p ) != seen.end() )
|
||||
continue;
|
||||
|
||||
seen.insert( &p );
|
||||
|
||||
wxString msg = wxString::Format( "Remaining: %d,%d,", p.x, p.y );
|
||||
VERTEX* q = p.next;
|
||||
|
||||
do
|
||||
{
|
||||
msg += wxString::Format( "%d,%d,", q->x, q->y );
|
||||
seen.insert( q );
|
||||
q = q->next;
|
||||
} while ( q != &p );
|
||||
|
||||
msg.RemoveLast();
|
||||
wxLogTrace( TRIANGULATE_TRACE, msg );
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Iterate through the list to remove NULL triangles if they exist.
|
||||
*
|
||||
|
@ -583,7 +631,7 @@ private:
|
|||
{
|
||||
VERTEX* nextZ = origPoly->nextZ;
|
||||
|
||||
if( nextZ && *nextZ == *origPoly )
|
||||
if( nextZ && nextZ != origPoly->next && nextZ != origPoly->prev && *nextZ == *origPoly )
|
||||
{
|
||||
std::swap( origPoly->next, nextZ->next );
|
||||
origPoly->next->prev = origPoly;
|
||||
|
@ -596,7 +644,7 @@ private:
|
|||
|
||||
VERTEX* prevZ = origPoly->prevZ;
|
||||
|
||||
if( prevZ && *prevZ == *origPoly )
|
||||
if( prevZ && prevZ != origPoly->next && prevZ != origPoly->prev && *prevZ == *origPoly )
|
||||
{
|
||||
std::swap( origPoly->next, prevZ->next );
|
||||
origPoly->next->prev = origPoly;
|
||||
|
|
|
@ -297,6 +297,15 @@ public:
|
|||
*/
|
||||
int ShapeCount() const;
|
||||
|
||||
|
||||
/**
|
||||
* Simplify the line chain by removing colinear adjacent segments and duplicate vertices.
|
||||
*
|
||||
* @param aMaxError is the maximum error in internal units. Setting to 0 means that the
|
||||
* points must be exactly co-linear to be removed.
|
||||
*/
|
||||
void Simplify( int aMaxError = 0 );
|
||||
|
||||
/**
|
||||
* Return the number of points (vertices) in this line chain.
|
||||
*
|
||||
|
@ -640,14 +649,6 @@ public:
|
|||
*/
|
||||
const std::optional<INTERSECTION> SelfIntersecting() const;
|
||||
|
||||
/**
|
||||
* Simplify the line chain by removing colinear adjacent segments and duplicate vertices.
|
||||
*
|
||||
* @param aRemoveColinear controls the removal of colinear adjacent segments.
|
||||
* @return reference to this line chain.
|
||||
*/
|
||||
SHAPE_LINE_CHAIN& Simplify( bool aRemoveColinear = true );
|
||||
|
||||
/**
|
||||
* Find the segment nearest the given point.
|
||||
*
|
||||
|
|
|
@ -1083,6 +1083,14 @@ public:
|
|||
/// For \a aFastMode meaning, see function booleanOp
|
||||
void Simplify( POLYGON_MODE aFastMode );
|
||||
|
||||
/**
|
||||
* Simplifies the lines in the polyset. This checks intermediate points to see if they are
|
||||
* collinear with their neighbors, and removes them if they are.
|
||||
*
|
||||
* @param aMaxError is the maximum error to allow when simplifying the lines.
|
||||
*/
|
||||
void SimplifyOutlines( int aMaxError = 0 );
|
||||
|
||||
/**
|
||||
* Convert a self-intersecting polygon to one (or more) non self-intersecting polygon(s).
|
||||
*
|
||||
|
|
|
@ -274,4 +274,30 @@ inline bool InterceptsNegativeX( double aStartAngle, double aEndAngle )
|
|||
return aStartAngle < 180.0 && end > 180.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the area of a parallelogram defined by \a aPointA, \a aPointB, and \a aPointC.
|
||||
* B ______________________
|
||||
* / /
|
||||
* / /
|
||||
* /______________________/
|
||||
* A C
|
||||
* The area of a parallelogram is the cross product of the vectors A->B and A->C.
|
||||
* The order of the vertices is not important, the result will be the same (modulo sign).
|
||||
*/
|
||||
template <class T>
|
||||
inline typename VECTOR2<T>::extended_type
|
||||
ParallelogramArea( const VECTOR2<T>& aPointA, const VECTOR2<T>& aPointB, const VECTOR2<T>& aPointC )
|
||||
{
|
||||
VECTOR2<T> v1 = aPointB - aPointA;
|
||||
VECTOR2<T> v2 = aPointC - aPointA;
|
||||
return v1.Cross( v2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if a point hits a line segment within a given distance. This is a faster version of
|
||||
* TestSegmentHit() that does not calculate the distance.
|
||||
*/
|
||||
bool TestSegmentHitFast( const VECTOR2I& aRefPoint, const VECTOR2I& aStart, const VECTOR2I& aEnd,
|
||||
int aDist );
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1824,108 +1824,6 @@ const std::optional<SHAPE_LINE_CHAIN::INTERSECTION> SHAPE_LINE_CHAIN::SelfInters
|
|||
}
|
||||
|
||||
|
||||
SHAPE_LINE_CHAIN& SHAPE_LINE_CHAIN::Simplify( bool aRemoveColinear )
|
||||
{
|
||||
std::vector<VECTOR2I> pts_unique;
|
||||
std::vector<std::pair<ssize_t, ssize_t>> shapes_unique;
|
||||
|
||||
// Always try to keep at least 2 points otherwise, we're not really a line
|
||||
if( PointCount() < 3 )
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
else if( PointCount() == 3 )
|
||||
{
|
||||
if( m_points[0] == m_points[1] )
|
||||
Remove( 1 );
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int np = PointCount();
|
||||
|
||||
// stage 1: eliminate duplicate vertices
|
||||
while( i < np )
|
||||
{
|
||||
int j = i + 1;
|
||||
|
||||
// We can eliminate duplicate vertices as long as they are part of the same shape, OR if
|
||||
// one of them is part of a shape and one is not.
|
||||
while( j < np && m_points[i] == m_points[j] &&
|
||||
( m_shapes[i] == m_shapes[j] ||
|
||||
m_shapes[i] == SHAPES_ARE_PT ||
|
||||
m_shapes[j] == SHAPES_ARE_PT ) )
|
||||
{
|
||||
j++;
|
||||
}
|
||||
|
||||
std::pair<ssize_t,ssize_t> shapeToKeep = m_shapes[i];
|
||||
|
||||
if( shapeToKeep == SHAPES_ARE_PT )
|
||||
shapeToKeep = m_shapes[j - 1];
|
||||
|
||||
assert( shapeToKeep.first < static_cast<int>( m_arcs.size() ) );
|
||||
assert( shapeToKeep.second < static_cast<int>( m_arcs.size() ) );
|
||||
|
||||
pts_unique.push_back( CPoint( i ) );
|
||||
shapes_unique.push_back( shapeToKeep );
|
||||
|
||||
i = j;
|
||||
}
|
||||
|
||||
m_points.clear();
|
||||
m_shapes.clear();
|
||||
np = pts_unique.size();
|
||||
|
||||
i = 0;
|
||||
|
||||
// stage 2: eliminate colinear segments
|
||||
while( i < np - 2 )
|
||||
{
|
||||
const VECTOR2I p0 = pts_unique[i];
|
||||
int n = i;
|
||||
|
||||
if( aRemoveColinear && shapes_unique[i] == SHAPES_ARE_PT
|
||||
&& shapes_unique[i + 1] == SHAPES_ARE_PT )
|
||||
{
|
||||
while( n < np - 2
|
||||
&& ( SEG( p0, pts_unique[n + 2] ).LineDistance( pts_unique[n + 1] ) <= 1
|
||||
|| SEG( p0, pts_unique[n + 2] ).Collinear( SEG( p0, pts_unique[n + 1] ) ) ) )
|
||||
n++;
|
||||
}
|
||||
|
||||
m_points.push_back( p0 );
|
||||
m_shapes.push_back( shapes_unique[i] );
|
||||
|
||||
if( n > i )
|
||||
i = n;
|
||||
|
||||
if( n == np - 2 )
|
||||
{
|
||||
m_points.push_back( pts_unique[np - 1] );
|
||||
m_shapes.push_back( shapes_unique[np - 1] );
|
||||
return *this;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if( np > 1 )
|
||||
{
|
||||
m_points.push_back( pts_unique[np - 2] );
|
||||
m_shapes.push_back( shapes_unique[np - 2] );
|
||||
}
|
||||
|
||||
m_points.push_back( pts_unique[np - 1] );
|
||||
m_shapes.push_back( shapes_unique[np - 1] );
|
||||
|
||||
assert( m_points.size() == m_shapes.size() );
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
const VECTOR2I SHAPE_LINE_CHAIN::NearestPoint( const VECTOR2I& aP,
|
||||
bool aAllowInternalShapePoints ) const
|
||||
{
|
||||
|
@ -2189,6 +2087,58 @@ double SHAPE_LINE_CHAIN::Area( bool aAbsolute ) const
|
|||
}
|
||||
|
||||
|
||||
void SHAPE_LINE_CHAIN::Simplify( int aMaxError )
|
||||
{
|
||||
if( PointCount() < 3 )
|
||||
return;
|
||||
|
||||
std::vector<VECTOR2I> new_points;
|
||||
std::vector<std::pair<ssize_t, ssize_t>> new_shapes;
|
||||
|
||||
new_points.reserve( m_points.size() );
|
||||
new_shapes.reserve( m_shapes.size() );
|
||||
|
||||
for( size_t ii = 0; ii < m_points.size(); )
|
||||
{
|
||||
new_points.push_back( m_points[ii] );
|
||||
new_shapes.push_back( m_shapes[ii] );
|
||||
size_t jj = ( ii + 1 ) % m_points.size();
|
||||
size_t kk = ( jj + 1 ) % m_points.size();
|
||||
|
||||
if( m_shapes[ii].first != SHAPE_IS_PT || m_shapes[jj].first != SHAPE_IS_PT
|
||||
|| m_shapes[kk].first != SHAPE_IS_PT )
|
||||
{
|
||||
++ii;
|
||||
continue;
|
||||
}
|
||||
|
||||
while( TestSegmentHitFast( m_points[jj], m_points[ii], m_points[kk], aMaxError )
|
||||
&& ii != kk && jj > ii )
|
||||
{
|
||||
jj = ( jj + 1 ) % m_points.size();
|
||||
kk = ( jj + 1 ) % m_points.size();
|
||||
}
|
||||
|
||||
if( ii == kk || jj <= ii )
|
||||
break;
|
||||
|
||||
ii = jj;
|
||||
}
|
||||
|
||||
// If we have only one point, we need to add a second point to make a line
|
||||
if( new_points.size() == 1 )
|
||||
{
|
||||
new_points.push_back( m_points.back() );
|
||||
new_shapes.push_back( m_shapes.back() );
|
||||
}
|
||||
|
||||
m_points.clear();
|
||||
m_shapes.clear();
|
||||
m_points = new_points;
|
||||
m_shapes = new_shapes;
|
||||
}
|
||||
|
||||
|
||||
void SHAPE_LINE_CHAIN::Split( const VECTOR2I& aStart, const VECTOR2I& aEnd, SHAPE_LINE_CHAIN& aPre,
|
||||
SHAPE_LINE_CHAIN& aMid, SHAPE_LINE_CHAIN& aPost ) const
|
||||
{
|
||||
|
|
|
@ -1874,6 +1874,18 @@ void SHAPE_POLY_SET::Simplify( POLYGON_MODE aFastMode )
|
|||
}
|
||||
|
||||
|
||||
void SHAPE_POLY_SET::SimplifyOutlines( int aMaxError )
|
||||
{
|
||||
for( POLYGON& paths : m_polys )
|
||||
{
|
||||
for( SHAPE_LINE_CHAIN& path : paths )
|
||||
{
|
||||
path.Simplify( aMaxError );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int SHAPE_POLY_SET::NormalizeAreaOutlines()
|
||||
{
|
||||
// We are expecting only one main outline, but this main outline can have holes
|
||||
|
@ -3025,11 +3037,23 @@ void SHAPE_POLY_SET::cacheTriangulation( bool aPartition, bool aSimplify,
|
|||
++pass;
|
||||
|
||||
if( pass == 1 )
|
||||
{
|
||||
polySet.Fracture( PM_FAST );
|
||||
}
|
||||
else if( pass == 2 )
|
||||
{
|
||||
polySet.SimplifyOutlines(
|
||||
ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel );
|
||||
}
|
||||
// In Clipper2, there is only one type of simplification
|
||||
else if( pass == 3 && !ADVANCED_CFG::GetCfg().m_UseClipper2 )
|
||||
{
|
||||
polySet.Fracture( PM_STRICTLY_SIMPLE );
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
triangulationValid = false;
|
||||
hintData = nullptr;
|
||||
|
|
|
@ -537,3 +537,32 @@ const VECTOR2I CalcArcCenter( const VECTOR2I& aStart, const VECTOR2I& aMid, cons
|
|||
return iCenter;
|
||||
}
|
||||
|
||||
|
||||
bool TestSegmentHitFast( const VECTOR2I& aRefPoint, const VECTOR2I& aStart, const VECTOR2I& aEnd,
|
||||
int aDist )
|
||||
{
|
||||
int xmin = std::min( aStart.x, aEnd.x );
|
||||
int xmax = std::max( aStart.x, aEnd.x );
|
||||
int ymin = std::min( aStart.y, aEnd.y );
|
||||
int ymax = std::max( aStart.y, aEnd.y );
|
||||
VECTOR2I delta = aStart - aRefPoint;
|
||||
|
||||
// Check if we are outside of the bounding box.
|
||||
if( ( ymin - aRefPoint.y > aDist ) || ( aRefPoint.y - ymax > aDist ) )
|
||||
return false;
|
||||
|
||||
if( ( xmin - aRefPoint.x > aDist ) || ( aRefPoint.x - xmax > aDist ) )
|
||||
return false;
|
||||
|
||||
// Eliminate easy cases.
|
||||
if( aStart.x == aEnd.x && aRefPoint.y > ymin && aRefPoint.y < ymax )
|
||||
return std::abs( delta.x ) <= aDist;
|
||||
|
||||
if( aStart.y == aEnd.y && aRefPoint.x > xmin && aRefPoint.x < xmax )
|
||||
return std::abs( delta.y ) <= aDist;
|
||||
|
||||
VECTOR2I::extended_type len = ( aEnd - aStart ).EuclideanNorm();
|
||||
VECTOR2I::extended_type area = len * aDist;
|
||||
|
||||
return std::abs( ParallelogramArea( aStart, aRefPoint, aEnd ) ) <= area;
|
||||
}
|
Loading…
Reference in New Issue