Update triangulation

This commit is contained in:
Seth Hillbrand 2024-03-20 15:47:44 -07:00
parent db12f2af49
commit 4f03bb2fb6
9 changed files with 215 additions and 112 deletions

View File

@ -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;

View File

@ -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;
///@}

View File

@ -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;

View File

@ -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.
*

View File

@ -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).
*

View File

@ -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

View File

@ -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
{

View File

@ -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;

View File

@ -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;
}