Update triangulation to handle poly-intersection
Polygon intersections happen against the original outline, not against
the currently remaining polygon. This avoids pathalogical cases
Adds new simplification system to avoid duplicated points
Adds new edge-splitting algorithm to provide additional fall-back
Verifies that polygon cuts do not swap holes for outlines (negative
area)
Fixes https://gitlab.com/kicad/code/kicad/-/issues/17559
(cherry picked from commit c3f6a84d66
)
This commit is contained in:
parent
76e0f94532
commit
92ffd898f5
|
@ -81,6 +81,8 @@ public:
|
||||||
/// therefore cannot be polygons
|
/// therefore cannot be polygons
|
||||||
VERTEX* firstVertex = createList( aPoly );
|
VERTEX* firstVertex = createList( aPoly );
|
||||||
|
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Created list with %f area", firstVertex->area() );
|
||||||
|
|
||||||
if( !firstVertex || firstVertex->prev == firstVertex->next )
|
if( !firstVertex || firstVertex->prev == firstVertex->next )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -96,7 +98,10 @@ public:
|
||||||
auto retval = earcutList( firstVertex );
|
auto retval = earcutList( firstVertex );
|
||||||
|
|
||||||
if( !retval )
|
if( !retval )
|
||||||
|
{
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Tesselation failed, logging remaining vertices" );
|
||||||
logRemaining();
|
logRemaining();
|
||||||
|
}
|
||||||
|
|
||||||
m_vertices.clear();
|
m_vertices.clear();
|
||||||
return retval;
|
return retval;
|
||||||
|
@ -266,25 +271,29 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the signed area of the polygon connected to the current vertex
|
* Returns the signed area of the polygon connected to the current vertex,
|
||||||
*/
|
* optionally ending at a specified vertex.
|
||||||
double area() const
|
*/
|
||||||
|
double area( const VERTEX* aEnd = nullptr ) const
|
||||||
{
|
{
|
||||||
const VERTEX* p = this;
|
const VERTEX* p = this;
|
||||||
double a = 0.0;
|
double a = 0.0;
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
a += ( p->x * p->next->y - p->next->x * p->y );
|
a += ( p->x + p->next->x ) * ( p->next->y - p->y );
|
||||||
p = p->next;
|
p = p->next;
|
||||||
} while( p != this );
|
} while( p != this && p != aEnd );
|
||||||
|
|
||||||
|
if( p != this )
|
||||||
|
a += ( p->x + aEnd->x ) * ( aEnd->y - p->y );
|
||||||
|
|
||||||
return a / 2;
|
return a / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t i;
|
const size_t i;
|
||||||
const double x;
|
double x;
|
||||||
const double y;
|
double y;
|
||||||
POLYGON_TRIANGULATION* parent;
|
POLYGON_TRIANGULATION* parent;
|
||||||
|
|
||||||
// previous and next vertices nodes in a polygon ring
|
// previous and next vertices nodes in a polygon ring
|
||||||
|
@ -335,34 +344,42 @@ private:
|
||||||
if( !p.next || p.next == &p || seen.find( &p ) != seen.end() )
|
if( !p.next || p.next == &p || seen.find( &p ) != seen.end() )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
seen.insert( &p );
|
logVertices( &p, &seen );
|
||||||
|
|
||||||
// Don't both logging tiny areas
|
|
||||||
if( std::abs( p.area() ) < 10 )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int count = 1;
|
|
||||||
wxString msg = wxString::Format( "Remaining: %d,%d,", static_cast<int>( p.x ),
|
|
||||||
static_cast<int>( p.y ) );
|
|
||||||
VERTEX* q = p.next;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
msg += wxString::Format( "%d,%d,", static_cast<int>( q->x ),
|
|
||||||
static_cast<int>( q->y ) );
|
|
||||||
seen.insert( q );
|
|
||||||
q = q->next;
|
|
||||||
count++;
|
|
||||||
} while( q != &p );
|
|
||||||
|
|
||||||
// Don't log anything that only has 2 or fewer points
|
|
||||||
if( count < 3 )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
msg.RemoveLast();
|
|
||||||
wxLogTrace( TRIANGULATE_TRACE, msg );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void logVertices( VERTEX* aStart, std::set<VERTEX*>* aSeen )
|
||||||
|
{
|
||||||
|
if( aSeen && aSeen->count( aStart ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( aSeen )
|
||||||
|
aSeen->insert( aStart );
|
||||||
|
|
||||||
|
int count = 1;
|
||||||
|
VERTEX* p = aStart->next;
|
||||||
|
wxString msg = wxString::Format( "Vertices: %d,%d,", static_cast<int>( aStart->x ),
|
||||||
|
static_cast<int>( aStart->y ) );
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
msg += wxString::Format( "%d,%d,", static_cast<int>( p->x ), static_cast<int>( p->y ) );
|
||||||
|
|
||||||
|
if( aSeen )
|
||||||
|
aSeen->insert( p );
|
||||||
|
|
||||||
|
p = p->next;
|
||||||
|
count++;
|
||||||
|
} while( p != aStart );
|
||||||
|
|
||||||
|
if( count < 3 ) // Don't log anything that only has 2 or fewer points
|
||||||
|
return;
|
||||||
|
|
||||||
|
msg.RemoveLast();
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, msg );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate through the list to remove NULL triangles if they exist.
|
* Iterate through the list to remove NULL triangles if they exist.
|
||||||
*
|
*
|
||||||
|
@ -374,10 +391,18 @@ private:
|
||||||
{
|
{
|
||||||
VERTEX* retval = nullptr;
|
VERTEX* retval = nullptr;
|
||||||
VERTEX* p = aStart->next;
|
VERTEX* p = aStart->next;
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
while( p != aStart )
|
while( p != aStart )
|
||||||
{
|
{
|
||||||
if( *p == *( p->next ) || area( p->prev, p, p->next ) == 0.0 )
|
// We make a dummy triangle that is actually part of the existing line segment
|
||||||
|
// and measure its area. This will not be exactly zero due to floating point
|
||||||
|
// errors. We then look for areas that are less than 4 times the area of the
|
||||||
|
// dummy triangle. For small triangles, this is a small number
|
||||||
|
VERTEX tmp( 0, 0.5 * ( p->prev->x + p->next->x ), 0.5 * ( p->prev->y + p->next->y ), this );
|
||||||
|
double null_area = 4.0 * std::abs( area( p->prev, &tmp, p->next ) );
|
||||||
|
|
||||||
|
if( *p == *( p->next ) || std::abs( area( p->prev, p, p->next ) ) <= null_area )
|
||||||
{
|
{
|
||||||
// This is a spike, remove it, leaving only one point
|
// This is a spike, remove it, leaving only one point
|
||||||
if( *( p->next ) == *( p->prev ) )
|
if( *( p->next ) == *( p->prev ) )
|
||||||
|
@ -386,6 +411,7 @@ private:
|
||||||
p = p->prev;
|
p = p->prev;
|
||||||
p->next->remove();
|
p->next->remove();
|
||||||
retval = aStart;
|
retval = aStart;
|
||||||
|
++count;
|
||||||
|
|
||||||
if( p == p->next )
|
if( p == p->next )
|
||||||
break;
|
break;
|
||||||
|
@ -398,61 +424,29 @@ private:
|
||||||
|
|
||||||
// We needed an end point above that wouldn't be removed, so
|
// We needed an end point above that wouldn't be removed, so
|
||||||
// here we do the final check for this as a Steiner point
|
// here we do the final check for this as a Steiner point
|
||||||
if( area( aStart->prev, aStart, aStart->next ) == 0.0 )
|
VERTEX tmp( 0, 0.5 * ( aStart->prev->x + aStart->next->x ),
|
||||||
|
0.5 * ( aStart->prev->y + aStart->next->y ), this );
|
||||||
|
double null_area = 4.0 * std::abs( area( aStart->prev, &tmp, aStart->next ) );
|
||||||
|
|
||||||
|
if( std::abs( area( aStart->prev, aStart, aStart->next ) ) <= null_area )
|
||||||
{
|
{
|
||||||
retval = p->next;
|
retval = p->next;
|
||||||
p->remove();
|
p->remove();
|
||||||
|
++count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Removed %zu NULL triangles", count );
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Take a Clipper path and converts it into a circular, doubly-linked list for triangulation.
|
|
||||||
*/
|
|
||||||
VERTEX* createList( const ClipperLib::Path& aPath )
|
|
||||||
{
|
|
||||||
VERTEX* tail = nullptr;
|
|
||||||
double sum = 0.0;
|
|
||||||
auto len = aPath.size();
|
|
||||||
|
|
||||||
// Check for winding order
|
|
||||||
for( size_t i = 0; i < len; i++ )
|
|
||||||
{
|
|
||||||
auto p1 = aPath.at( i );
|
|
||||||
auto p2 = aPath.at( ( i + 1 ) < len ? i + 1 : 0 );
|
|
||||||
|
|
||||||
sum += ( ( p2.X - p1.X ) * ( p2.Y + p1.Y ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( sum <= 0.0 )
|
|
||||||
{
|
|
||||||
for( auto point : aPath )
|
|
||||||
tail = insertVertex( VECTOR2I( point.X, point.Y ), tail );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for( size_t i = 0; i < len; i++ )
|
|
||||||
{
|
|
||||||
auto p = aPath.at( len - i - 1 );
|
|
||||||
tail = insertVertex( VECTOR2I( p.X, p.Y ), tail );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( tail && ( *tail == *tail->next ) )
|
|
||||||
{
|
|
||||||
tail->next->remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
return tail;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take a #SHAPE_LINE_CHAIN and links each point into a circular, doubly-linked list.
|
* Take a #SHAPE_LINE_CHAIN and links each point into a circular, doubly-linked list.
|
||||||
*/
|
*/
|
||||||
VERTEX* createList( const SHAPE_LINE_CHAIN& points )
|
VERTEX* createList( const SHAPE_LINE_CHAIN& points )
|
||||||
{
|
{
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Creating list from %d points", points.PointCount() );
|
||||||
|
|
||||||
VERTEX* tail = nullptr;
|
VERTEX* tail = nullptr;
|
||||||
double sum = 0.0;
|
double sum = 0.0;
|
||||||
|
|
||||||
|
@ -465,17 +459,34 @@ private:
|
||||||
sum += ( ( p2.x - p1.x ) * ( p2.y + p1.y ) );
|
sum += ( ( p2.x - p1.x ) * ( p2.y + p1.y ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VECTOR2I last_pt{ std::numeric_limits<int>::max(), std::numeric_limits<int>::max() };
|
||||||
|
VECTOR2I::extended_type sq_dist = ADVANCED_CFG::GetCfg().m_TriangulateSimplificationLevel;
|
||||||
|
sq_dist *= sq_dist;
|
||||||
|
|
||||||
|
auto addVertex = [&]( int i )
|
||||||
|
{
|
||||||
|
const VECTOR2I& pt = points.CPoint( i );
|
||||||
|
VECTOR2I diff = pt - last_pt;
|
||||||
|
if( diff.SquaredEuclideanNorm() > sq_dist )
|
||||||
|
{
|
||||||
|
tail = insertVertex( pt, tail );
|
||||||
|
last_pt = pt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if( sum > 0.0 )
|
if( sum > 0.0 )
|
||||||
for( int i = points.PointCount() - 1; i >= 0; i--)
|
{
|
||||||
tail = insertVertex( points.CPoint( i ), tail );
|
for( int i = points.PointCount() - 1; i >= 0; i-- )
|
||||||
|
addVertex( i );
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
for( int i = 0; i < points.PointCount(); i++ )
|
for( int i = 0; i < points.PointCount(); i++ )
|
||||||
tail = insertVertex( points.CPoint( i ), tail );
|
addVertex( i );
|
||||||
|
}
|
||||||
|
|
||||||
if( tail && ( *tail == *tail->next ) )
|
if( tail && ( *tail == *tail->next ) )
|
||||||
{
|
|
||||||
tail->next->remove();
|
tail->next->remove();
|
||||||
}
|
|
||||||
|
|
||||||
return tail;
|
return tail;
|
||||||
}
|
}
|
||||||
|
@ -495,12 +506,15 @@ private:
|
||||||
*/
|
*/
|
||||||
bool earcutList( VERTEX* aPoint, int pass = 0 )
|
bool earcutList( VERTEX* aPoint, int pass = 0 )
|
||||||
{
|
{
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "earcutList starting at %p for pass %d", aPoint, pass );
|
||||||
|
|
||||||
if( !aPoint )
|
if( !aPoint )
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
VERTEX* stop = aPoint;
|
VERTEX* stop = aPoint;
|
||||||
VERTEX* prev;
|
VERTEX* prev;
|
||||||
VERTEX* next;
|
VERTEX* next;
|
||||||
|
int internal_pass = 1;
|
||||||
|
|
||||||
while( aPoint->prev != aPoint->next )
|
while( aPoint->prev != aPoint->next )
|
||||||
{
|
{
|
||||||
|
@ -511,7 +525,14 @@ private:
|
||||||
{
|
{
|
||||||
// Tiny ears cannot be seen on the screen
|
// Tiny ears cannot be seen on the screen
|
||||||
if( !isTooSmall( aPoint ) )
|
if( !isTooSmall( aPoint ) )
|
||||||
|
{
|
||||||
m_result.AddTriangle( prev->i, aPoint->i, next->i );
|
m_result.AddTriangle( prev->i, aPoint->i, next->i );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Ignoring tiny ear with area %f",
|
||||||
|
area( prev, aPoint, next ) );
|
||||||
|
}
|
||||||
|
|
||||||
aPoint->remove();
|
aPoint->remove();
|
||||||
|
|
||||||
|
@ -528,6 +549,9 @@ private:
|
||||||
locallyInside( prev, nextNext ) &&
|
locallyInside( prev, nextNext ) &&
|
||||||
locallyInside( nextNext, prev ) )
|
locallyInside( nextNext, prev ) )
|
||||||
{
|
{
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE,
|
||||||
|
"Local intersection detected. Merging minor triangle with area %f",
|
||||||
|
area( prev, aPoint, nextNext ) );
|
||||||
m_result.AddTriangle( prev->i, aPoint->i, nextNext->i );
|
m_result.AddTriangle( prev->i, aPoint->i, nextNext->i );
|
||||||
|
|
||||||
// remove two nodes involved
|
// remove two nodes involved
|
||||||
|
@ -548,16 +572,35 @@ private:
|
||||||
*/
|
*/
|
||||||
if( aPoint == stop )
|
if( aPoint == stop )
|
||||||
{
|
{
|
||||||
// First, try to remove the remaining steiner points
|
VERTEX* newPoint;
|
||||||
// If aPoint is a steiner, we need to re-assign both the start and stop points
|
|
||||||
if( auto newPoint = removeNullTriangles( aPoint ) )
|
// Removing null triangles will remove steiner points as well as colinear points
|
||||||
|
// that are three in a row. Because our next step is to subdivide the polygon,
|
||||||
|
// we need to allow it to add the subdivided points first. This is why we only
|
||||||
|
// run the RemoveNullTriangles function after the first pass.
|
||||||
|
if( ( internal_pass == 2 ) && ( newPoint = removeNullTriangles( aPoint ) ) )
|
||||||
{
|
{
|
||||||
aPoint = newPoint;
|
aPoint = newPoint;
|
||||||
stop = newPoint;
|
stop = newPoint;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
++internal_pass;
|
||||||
|
|
||||||
|
// This will subdivide the polygon 2 times. The first pass will add enough points
|
||||||
|
// such that each edge is less than the average edge length. If this doesn't work
|
||||||
|
// The next pass will remove the null triangles (above) and subdivide the polygon
|
||||||
|
// again, this time adding one point to each long edge (and thereby changing the locations)
|
||||||
|
if( internal_pass < 4 )
|
||||||
|
{
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Subdividing polygon" );
|
||||||
|
subdividePolygon( aPoint, internal_pass );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// If we don't have any NULL triangles left, cut the polygon in two and try again
|
// If we don't have any NULL triangles left, cut the polygon in two and try again
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Splitting polygon" );
|
||||||
|
|
||||||
if( !splitPolygon( aPoint ) )
|
if( !splitPolygon( aPoint ) )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -661,6 +704,59 @@ private:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new vertex halfway between each existing pair of vertices.
|
||||||
|
*/
|
||||||
|
void subdividePolygon( VERTEX* aStart, int pass = 0 )
|
||||||
|
{
|
||||||
|
VERTEX* p = aStart;
|
||||||
|
|
||||||
|
struct VertexComparator {
|
||||||
|
bool operator()(const std::pair<VERTEX*,double>& a, const std::pair<VERTEX*,double>& b) const {
|
||||||
|
return a.second > b.second;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::set<std::pair<VERTEX*,double>, VertexComparator> longest;
|
||||||
|
double avg = 0.0;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
double len = ( p->x - p->next->x ) * ( p->x - p->next->x ) +
|
||||||
|
( p->y - p->next->y ) * ( p->y - p->next->y );
|
||||||
|
longest.emplace( p, len );
|
||||||
|
|
||||||
|
avg += len;
|
||||||
|
p = p->next;
|
||||||
|
} while (p != aStart);
|
||||||
|
|
||||||
|
avg /= longest.size();
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Average length: %f", avg );
|
||||||
|
|
||||||
|
for( auto it = longest.begin(); it != longest.end() && it->second > avg; ++it )
|
||||||
|
{
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Subdividing edge with length %f", it->second );
|
||||||
|
VERTEX* a = it->first;
|
||||||
|
VERTEX* b = a->next;
|
||||||
|
VERTEX* last = a;
|
||||||
|
|
||||||
|
// We adjust the number of divisions based on the pass in order to progressively
|
||||||
|
// subdivide the polygon when triangulation fails
|
||||||
|
int divisions = avg / it->second + 2 + pass;
|
||||||
|
double step = 1.0 / divisions;
|
||||||
|
|
||||||
|
for( int i = 1; i < divisions; i++ )
|
||||||
|
{
|
||||||
|
double x = a->x * ( 1.0 - step * i ) + b->x * ( step * i );
|
||||||
|
double y = a->y * ( 1.0 - step * i ) + b->y * ( step * i );
|
||||||
|
last = insertVertex( VECTOR2I( x, y ), last );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update z-order of the vertices
|
||||||
|
aStart->updateList();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If we cannot find an ear to slice in the current polygon list, we
|
* If we cannot find an ear to slice in the current polygon list, we
|
||||||
* use this to split the polygon into two separate lists and slice them each
|
* use this to split the polygon into two separate lists and slice them each
|
||||||
|
@ -671,39 +767,61 @@ private:
|
||||||
{
|
{
|
||||||
VERTEX* origPoly = start;
|
VERTEX* origPoly = start;
|
||||||
|
|
||||||
|
// If we have fewer than 4 points, we cannot split the polygon
|
||||||
|
if( !start || !start->next || start->next == start->prev
|
||||||
|
|| start->next->next == start->prev )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Our first attempts to split the polygon will be at overlapping points.
|
// Our first attempts to split the polygon will be at overlapping points.
|
||||||
// These are natural split points and we only need to switch the loop directions
|
// These are natural split points and we only need to switch the loop directions
|
||||||
// to generate two new loops. Since they are overlapping, we are do not
|
// to generate two new loops. Since they are overlapping, we are do not
|
||||||
// need to create a new segment to disconnect the two loops.
|
// need to create a new segment to disconnect the two loops.
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
VERTEX* nextZ = origPoly->nextZ;
|
std::vector<VERTEX*> overlapPoints;
|
||||||
|
VERTEX* z_pt = origPoly;
|
||||||
|
|
||||||
if( nextZ && nextZ != origPoly->next && nextZ != origPoly->prev && *nextZ == *origPoly )
|
while ( z_pt->prevZ && *z_pt->prevZ == *origPoly )
|
||||||
|
z_pt = z_pt->prevZ;
|
||||||
|
|
||||||
|
overlapPoints.push_back( z_pt );
|
||||||
|
|
||||||
|
while( z_pt->nextZ && *z_pt->nextZ == *origPoly )
|
||||||
{
|
{
|
||||||
std::swap( origPoly->next, nextZ->next );
|
z_pt = z_pt->nextZ;
|
||||||
origPoly->next->prev = origPoly;
|
overlapPoints.push_back( z_pt );
|
||||||
nextZ->next->prev = nextZ;
|
|
||||||
|
|
||||||
origPoly->updateList();
|
|
||||||
nextZ->updateList();
|
|
||||||
return earcutList( origPoly ) && earcutList( nextZ );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VERTEX* prevZ = origPoly->prevZ;
|
if( overlapPoints.size() != 2 || overlapPoints[0]->next == overlapPoints[1]
|
||||||
|
|| overlapPoints[0]->prev == overlapPoints[1] )
|
||||||
if( prevZ && prevZ != origPoly->next && prevZ != origPoly->prev && *prevZ == *origPoly )
|
|
||||||
{
|
{
|
||||||
std::swap( origPoly->next, prevZ->next );
|
origPoly = origPoly->next;
|
||||||
origPoly->next->prev = origPoly;
|
continue;
|
||||||
prevZ->next->prev = prevZ;
|
|
||||||
|
|
||||||
origPoly->updateList();
|
|
||||||
prevZ->updateList();
|
|
||||||
return earcutList( origPoly ) && earcutList( prevZ );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
origPoly = origPoly->next;
|
if( overlapPoints[0]->area( overlapPoints[1] ) < 0 || overlapPoints[1]->area( overlapPoints[0] ) < 0 )
|
||||||
|
{
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Split generated a hole, skipping" );
|
||||||
|
origPoly = origPoly->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Splitting at overlap point %f, %f", overlapPoints[0]->x, overlapPoints[0]->y );
|
||||||
|
std::swap( overlapPoints[0]->next, overlapPoints[1]->next );
|
||||||
|
overlapPoints[0]->next->prev = overlapPoints[0];
|
||||||
|
overlapPoints[1]->next->prev = overlapPoints[1];
|
||||||
|
|
||||||
|
overlapPoints[0]->updateList();
|
||||||
|
overlapPoints[1]->updateList();
|
||||||
|
logVertices( overlapPoints[0], nullptr );
|
||||||
|
logVertices( overlapPoints[1], nullptr );
|
||||||
|
bool retval = earcutList( overlapPoints[0] ) && earcutList( overlapPoints[1] );
|
||||||
|
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "%s at first overlap split", retval ? "Success" : "Failed" );
|
||||||
|
return retval;
|
||||||
|
|
||||||
|
|
||||||
} while ( origPoly != start );
|
} while ( origPoly != start );
|
||||||
|
|
||||||
|
@ -718,14 +836,17 @@ private:
|
||||||
while( marker != origPoly->prev )
|
while( marker != origPoly->prev )
|
||||||
{
|
{
|
||||||
// Find a diagonal line that is wholly enclosed by the polygon interior
|
// Find a diagonal line that is wholly enclosed by the polygon interior
|
||||||
if( origPoly->i != marker->i && goodSplit( origPoly, marker ) )
|
if( origPoly->next && origPoly->i != marker->i && goodSplit( origPoly, marker ) )
|
||||||
{
|
{
|
||||||
VERTEX* newPoly = origPoly->split( marker );
|
VERTEX* newPoly = origPoly->split( marker );
|
||||||
|
|
||||||
origPoly->updateList();
|
origPoly->updateList();
|
||||||
newPoly->updateList();
|
newPoly->updateList();
|
||||||
|
|
||||||
return earcutList( origPoly ) && earcutList( newPoly );
|
bool retval = earcutList( origPoly ) && earcutList( newPoly );
|
||||||
|
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "%s at split", retval ? "Success" : "Failed" );
|
||||||
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
marker = marker->next;
|
marker = marker->next;
|
||||||
|
@ -734,6 +855,7 @@ private:
|
||||||
origPoly = origPoly->next;
|
origPoly = origPoly->next;
|
||||||
} while( origPoly != start );
|
} while( origPoly != start );
|
||||||
|
|
||||||
|
wxLogTrace( TRIANGULATE_TRACE, "Could not find a valid split point" );
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -754,9 +876,9 @@ private:
|
||||||
bool local_split = locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );
|
bool local_split = locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );
|
||||||
bool same_dir = area( a->prev, a, b->prev ) != 0.0 || area( a, b->prev, b ) != 0.0;
|
bool same_dir = area( a->prev, a, b->prev ) != 0.0 || area( a, b->prev, b ) != 0.0;
|
||||||
bool has_len = ( *a == *b ) && area( a->prev, a, a->next ) > 0 && area( b->prev, b, b->next ) > 0;
|
bool has_len = ( *a == *b ) && area( a->prev, a, a->next ) > 0 && area( b->prev, b, b->next ) > 0;
|
||||||
|
bool pos_area = a->area( b ) > 0 && b->area( a ) > 0;
|
||||||
|
|
||||||
|
return no_intersect && local_split && ( same_dir || has_len ) && !a_on_edge && !b_on_edge && pos_area;
|
||||||
return no_intersect && local_split && ( same_dir || has_len ) && !a_on_edge && !b_on_edge;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -824,20 +946,25 @@ private:
|
||||||
*/
|
*/
|
||||||
bool intersectsPolygon( const VERTEX* a, const VERTEX* b ) const
|
bool intersectsPolygon( const VERTEX* a, const VERTEX* b ) const
|
||||||
{
|
{
|
||||||
const VERTEX* p = a->next;
|
for( auto it = m_vertices.begin(); it != m_vertices.end(); )
|
||||||
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
if( p->i != a->i &&
|
const VERTEX* p = &*it;
|
||||||
p->next->i != a->i &&
|
const VERTEX* q = &*( ++it );
|
||||||
p->i != b->i &&
|
|
||||||
p->next->i != b->i && intersects( p, p->next, a, b ) )
|
if( p->i == a->i || p->i == b->i || q->i == a->i || q->i == b->i )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if( intersects( p, q, a, b ) )
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
p = p->next;
|
if( m_vertices.front().i == a->i || m_vertices.front().i == b->i
|
||||||
} while( p != a );
|
|| m_vertices.back().i == a->i || m_vertices.back().i == b->i )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return intersects( a, b, &m_vertices.back(), &m_vertices.front() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -120,6 +120,18 @@ public:
|
||||||
virtual size_t GetPointCount() const override { return 3; }
|
virtual size_t GetPointCount() const override { return 3; }
|
||||||
virtual size_t GetSegmentCount() const override { return 3; }
|
virtual size_t GetSegmentCount() const override { return 3; }
|
||||||
|
|
||||||
|
double Area() const
|
||||||
|
{
|
||||||
|
VECTOR2I& aa = parent->m_vertices[a];
|
||||||
|
VECTOR2I& bb = parent->m_vertices[b];
|
||||||
|
VECTOR2I& cc = parent->m_vertices[c];
|
||||||
|
|
||||||
|
VECTOR2D ba = bb - aa;
|
||||||
|
VECTOR2D cb = cc - bb;
|
||||||
|
|
||||||
|
return std::abs( cb.Cross( ba ) * 0.5 );
|
||||||
|
}
|
||||||
|
|
||||||
int a;
|
int a;
|
||||||
int b;
|
int b;
|
||||||
int c;
|
int c;
|
||||||
|
|
|
@ -3309,6 +3309,11 @@ void SHAPE_POLY_SET::cacheTriangulation( bool aPartition, bool aSimplify,
|
||||||
triangulationValid = true;
|
triangulationValid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( triangulationValid && wxLog::IsLevelEnabled(wxLOG_Trace, wxLOG_COMPONENT) )
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return triangulationValid;
|
return triangulationValid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -46,6 +46,7 @@ set( QA_PCBNEW_SRCS
|
||||||
test_reference_image_load.cpp
|
test_reference_image_load.cpp
|
||||||
test_save_load.cpp
|
test_save_load.cpp
|
||||||
test_tracks_cleaner.cpp
|
test_tracks_cleaner.cpp
|
||||||
|
test_triangulation.cpp
|
||||||
test_zone_filler.cpp
|
test_zone_filler.cpp
|
||||||
|
|
||||||
drc/test_custom_rule_severities.cpp
|
drc/test_custom_rule_severities.cpp
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, you may find one here:
|
||||||
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||||||
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
||||||
|
* or you may write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <qa_utils/wx_utils/unit_test_utils.h>
|
||||||
|
#include <pcbnew_utils/board_test_utils.h>
|
||||||
|
#include <board.h>
|
||||||
|
#include <board_design_settings.h>
|
||||||
|
#include <pad.h>
|
||||||
|
#include <pcb_track.h>
|
||||||
|
#include <footprint.h>
|
||||||
|
#include <zone.h>
|
||||||
|
#include <drc/drc_item.h>
|
||||||
|
#include <settings/settings_manager.h>
|
||||||
|
|
||||||
|
|
||||||
|
struct TRIANGULATE_TEST_FIXTURE
|
||||||
|
{
|
||||||
|
TRIANGULATE_TEST_FIXTURE() :
|
||||||
|
m_settingsManager( true /* headless */ )
|
||||||
|
{ }
|
||||||
|
|
||||||
|
SETTINGS_MANAGER m_settingsManager;
|
||||||
|
std::unique_ptr<BOARD> m_board;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE( RegressionTriangulationTests, TRIANGULATE_TEST_FIXTURE )
|
||||||
|
{
|
||||||
|
std::vector<wxString> tests = {
|
||||||
|
"issue2568",
|
||||||
|
"issue5313",
|
||||||
|
"issue5320",
|
||||||
|
"issue5567",
|
||||||
|
"issue5830",
|
||||||
|
"issue6039",
|
||||||
|
"issue6260",
|
||||||
|
"issue7086",
|
||||||
|
"issue14294",
|
||||||
|
"issue17559"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
for( const wxString& relPath : tests )
|
||||||
|
{
|
||||||
|
KI_TEST::LoadBoard( m_settingsManager, relPath, m_board );
|
||||||
|
|
||||||
|
BOARD_DESIGN_SETTINGS& bds = m_board->GetDesignSettings();
|
||||||
|
|
||||||
|
for( ZONE* zone : m_board->Zones() )
|
||||||
|
{
|
||||||
|
for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq())
|
||||||
|
{
|
||||||
|
auto poly = zone->GetFilledPolysList( layer );
|
||||||
|
double area = poly->Area();
|
||||||
|
double tri_area = 0.0;
|
||||||
|
|
||||||
|
for( int ii = 0; ii < poly->TriangulatedPolyCount(); ii++ )
|
||||||
|
{
|
||||||
|
const auto tri_poly = poly->TriangulatedPolygon( ii );
|
||||||
|
|
||||||
|
for( auto& tri : tri_poly->Triangles() )
|
||||||
|
tri_area += tri.Area();
|
||||||
|
}
|
||||||
|
|
||||||
|
double diff = std::abs( area - tri_area );
|
||||||
|
|
||||||
|
// The difference should be less than 1e-4 square mm
|
||||||
|
BOOST_CHECK_MESSAGE( diff < 1e8, "Triangulation area mismatch in " + relPath + " layer " + LayerName( layer ) + " difference: " + std::to_string( diff ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue