mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-13 17:53:11 +02:00
Fix Chessboard splitting
Due to a bug(?) in Clipper2, 45° collinear edges may not be detected. See https://github.com/AngusJohnson/Clipper2/issues/1008 for current status. In the meantime, we pre-process these to remove the extraneous joints preventing our triangulation from getting mixed up Fixes https://gitlab.com/kicad/code/kicad/-/issues/18176
This commit is contained in:
parent
ea8206eca5
commit
408e1feae2
@ -1506,6 +1506,8 @@ private:
|
||||
void inflateLine2( const SHAPE_LINE_CHAIN& aLine, int aAmount, int aCircleSegCount,
|
||||
CORNER_STRATEGY aCornerStrategy, bool aSimplify = false );
|
||||
|
||||
void splitCollinearOutlines();
|
||||
|
||||
/**
|
||||
* This is the engine to execute all polygon boolean transforms (AND, OR, ... and polygon
|
||||
* simplification (merging overlapping polygons).
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include <unordered_set>
|
||||
#include <utility> // for swap, move
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include <clipper2/clipper.h>
|
||||
#include <geometry/geometry_utils.h>
|
||||
@ -49,6 +50,7 @@
|
||||
#include <geometry/shape.h>
|
||||
#include <geometry/shape_line_chain.h>
|
||||
#include <geometry/shape_poly_set.h>
|
||||
#include <geometry/rtree.h>
|
||||
#include <math/box2.h> // for BOX2I
|
||||
#include <math/util.h> // for KiROUND, rescale
|
||||
#include <math/vector2d.h> // for VECTOR2I, VECTOR2D, VECTOR2
|
||||
@ -69,6 +71,46 @@
|
||||
#define ENABLECACHEFRIENDLYFRACTURE ADVANCED_CFG::GetCfg().m_EnableCacheFriendlyFracture
|
||||
#endif
|
||||
|
||||
static bool segmentsColinearOverlap( const SEG& a, const SEG& b, VECTOR2I& s, VECTOR2I& e )
|
||||
{
|
||||
const VECTOR2I da = a.B - a.A;
|
||||
const VECTOR2I db = b.B - b.A;
|
||||
|
||||
if( da.Cross( db ) != 0 )
|
||||
return false;
|
||||
|
||||
if( da.Cross( b.A - a.A ) != 0 )
|
||||
return false;
|
||||
|
||||
int axis = std::abs( da.x ) >= std::abs( da.y ) ? 0 : 1;
|
||||
|
||||
int aMin = axis == 0 ? std::min( a.A.x, a.B.x ) : std::min( a.A.y, a.B.y );
|
||||
int aMax = axis == 0 ? std::max( a.A.x, a.B.x ) : std::max( a.A.y, a.B.y );
|
||||
int bMin = axis == 0 ? std::min( b.A.x, b.B.x ) : std::min( b.A.y, b.B.y );
|
||||
int bMax = axis == 0 ? std::max( b.A.x, b.B.x ) : std::max( b.A.y, b.B.y );
|
||||
|
||||
int lo = std::max( aMin, bMin );
|
||||
int hi = std::min( aMax, bMax );
|
||||
|
||||
if( hi <= lo )
|
||||
return false;
|
||||
|
||||
std::array<VECTOR2I,4> pts = { a.A, a.B, b.A, b.B };
|
||||
|
||||
std::sort( pts.begin(), pts.end(), [axis]( const VECTOR2I& p, const VECTOR2I& q )
|
||||
{
|
||||
if( axis == 0 )
|
||||
return p.x < q.x || ( p.x == q.x && p.y < q.y );
|
||||
else
|
||||
return p.y < q.y || ( p.y == q.y && p.x < q.x );
|
||||
} );
|
||||
|
||||
s = pts[1];
|
||||
e = pts[2];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SHAPE_POLY_SET::SHAPE_POLY_SET() :
|
||||
SHAPE( SH_POLY_SET )
|
||||
{
|
||||
@ -1860,8 +1902,110 @@ void SHAPE_POLY_SET::Unfracture()
|
||||
}
|
||||
|
||||
|
||||
void SHAPE_POLY_SET::splitCollinearOutlines()
|
||||
{
|
||||
for( size_t polyIdx = 0; polyIdx < m_polys.size(); ++polyIdx )
|
||||
{
|
||||
bool changed = true;
|
||||
|
||||
while( changed )
|
||||
{
|
||||
changed = false;
|
||||
|
||||
SHAPE_LINE_CHAIN& outline = m_polys[polyIdx][0];
|
||||
intptr_t count = outline.PointCount();
|
||||
|
||||
RTree<intptr_t, intptr_t, 2, intptr_t> rtree;
|
||||
|
||||
for( intptr_t i = 0; i < count; ++i )
|
||||
{
|
||||
const VECTOR2I& a = outline.CPoint( i );
|
||||
const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
|
||||
intptr_t min[2] = { std::min( a.x, b.x ), std::min( a.y, b.y ) };
|
||||
intptr_t max[2] = { std::max( a.x, b.x ), std::max( a.y, b.y ) };
|
||||
rtree.Insert( min, max, i );
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
int segA = -1;
|
||||
int segB = -1;
|
||||
VECTOR2I s, e;
|
||||
|
||||
for( intptr_t i = 0; i < count && !found; ++i )
|
||||
{
|
||||
const VECTOR2I& a = outline.CPoint( i );
|
||||
const VECTOR2I& b = outline.CPoint( ( i + 1 ) % count );
|
||||
SEG seg( a, b );
|
||||
intptr_t min[2] = { std::min( a.x, b.x ), std::min( a.y, b.y ) };
|
||||
intptr_t max[2] = { std::max( a.x, b.x ), std::max( a.y, b.y ) };
|
||||
|
||||
auto visitor =
|
||||
[&]( const int& j ) -> bool
|
||||
{
|
||||
if( j == i || j == ( ( i + 1 ) % count ) || j == ( ( i + count - 1 ) % count ) )
|
||||
return true;
|
||||
|
||||
VECTOR2I oa = outline.CPoint( j );
|
||||
VECTOR2I ob = outline.CPoint( ( j + 1 ) % count );
|
||||
SEG other( oa, ob );
|
||||
|
||||
if( segmentsColinearOverlap( seg, other, s, e ) )
|
||||
{
|
||||
segA = i;
|
||||
segB = j;
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
rtree.Search( min, max, visitor );
|
||||
}
|
||||
|
||||
if( !found )
|
||||
break;
|
||||
|
||||
int a0 = segA;
|
||||
int a1 = ( segA + 1 ) % outline.PointCount();
|
||||
int b0 = segB;
|
||||
int b1 = ( segB + 1 ) % outline.PointCount();
|
||||
|
||||
SHAPE_LINE_CHAIN lc1;
|
||||
int idx = a1;
|
||||
lc1.Append( outline.CPoint( idx ) );
|
||||
while( idx != b0 )
|
||||
{
|
||||
idx = ( idx + 1 ) % outline.PointCount();
|
||||
lc1.Append( outline.CPoint( idx ) );
|
||||
}
|
||||
lc1.SetClosed( true );
|
||||
|
||||
SHAPE_LINE_CHAIN lc2;
|
||||
idx = b1;
|
||||
lc2.Append( outline.CPoint( idx ) );
|
||||
while( idx != a0 )
|
||||
{
|
||||
idx = ( idx + 1 ) % outline.PointCount();
|
||||
lc2.Append( outline.CPoint( idx ) );
|
||||
}
|
||||
lc2.SetClosed( true );
|
||||
|
||||
m_polys[polyIdx][0] = lc1;
|
||||
POLYGON np;
|
||||
np.push_back( lc2 );
|
||||
m_polys.push_back( np );
|
||||
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SHAPE_POLY_SET::Simplify()
|
||||
{
|
||||
splitCollinearOutlines();
|
||||
|
||||
SHAPE_POLY_SET empty;
|
||||
|
||||
booleanOp( Clipper2Lib::ClipType::Union, empty );
|
||||
|
@ -47,6 +47,7 @@ set( QA_KIMATH_SRCS
|
||||
geometry/test_shape_poly_set_collision.cpp
|
||||
geometry/test_shape_poly_set_distance.cpp
|
||||
geometry/test_shape_poly_set_iterator.cpp
|
||||
geometry/test_shape_poly_set_split_outlines.cpp
|
||||
geometry/test_shape_line_chain.cpp
|
||||
geometry/test_shape_line_chain_collision.cpp
|
||||
geometry/test_vector_utils.cpp
|
||||
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2025 The 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 <vector>
|
||||
|
||||
#include <qa_utils/wx_utils/unit_test_utils.h>
|
||||
|
||||
#include <geometry/shape_poly_set.h>
|
||||
|
||||
#include <qa_utils/geometry/geometry.h>
|
||||
#include <geometry/geom_test_utils.h>
|
||||
|
||||
BOOST_AUTO_TEST_SUITE( ShapePolySetSplitOutlines )
|
||||
|
||||
BOOST_AUTO_TEST_CASE( SplitCoincidentOutlineOppositeDirection )
|
||||
{
|
||||
// ASCII art representation of the polygon:
|
||||
// 1-----------------0
|
||||
// | |
|
||||
// | |
|
||||
// | 4------5 |
|
||||
// | | | |
|
||||
// 2/9--3/8 | |
|
||||
// | | | |
|
||||
// 10---7------6----11
|
||||
|
||||
SHAPE_POLY_SET poly;
|
||||
SHAPE_LINE_CHAIN outline1( { 7600000, 9000000, 6600000, 9000000, 6600000, 8750000, 7000000, 8750000, 7000000,
|
||||
9000000, 7250000, 9000000, 7250000, 8500000, 7000000, 8500000, 7000000, 8750000,
|
||||
6600000, 8750000, 6600000, 8000000, 7600000, 8000000 } );
|
||||
outline1.SetClosed( true );
|
||||
poly.AddOutline( outline1 );
|
||||
|
||||
poly.Simplify();
|
||||
|
||||
BOOST_CHECK_EQUAL( poly.OutlineCount(), 1 );
|
||||
BOOST_CHECK_EQUAL( poly.Outline( 0 ).PointCount(), 10 ); //Why is this 10? I think it should probably be 8
|
||||
BOOST_CHECK( GEOM_TEST::IsPolySetValid( poly ) );
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( SplitCoincidentOutlineSameDirection )
|
||||
{
|
||||
// ASCII art representation of the polygon (approximate shape):
|
||||
// 8
|
||||
// / /
|
||||
// / /
|
||||
// / 5 /
|
||||
// / /\ /
|
||||
// 7 / 6/ \ /
|
||||
// 1-----------2\ /4
|
||||
// \ \/
|
||||
// \ /3
|
||||
// \ /
|
||||
// \ /
|
||||
// 0
|
||||
// This polygon has a self-intersecting/overlapping path that creates
|
||||
// coincident edges going in the same direction (points 7→2 and 2→7)
|
||||
// Original coordinates (scaled): 0:(99,83) 1:(93,89) 2:(80,86) 3:(94,85)
|
||||
// 4:(96,87) 5:(96,86) 6:(95,85) 7:(94,85) repeated points: 7:(94,85) 2:(80,86)
|
||||
|
||||
SHAPE_POLY_SET poly;
|
||||
SHAPE_LINE_CHAIN outline1( { 9912310, 8325057, 9288816, 8948550, 8000000, 8567586, 9428364,
|
||||
8547698, 9585009, 8652365, 9613140, 8624234, 9471719, 8482813,
|
||||
9428364, 8547698, 8000000, 8567586 } );
|
||||
outline1.SetClosed( true );
|
||||
poly.AddOutline( outline1 );
|
||||
|
||||
poly.Simplify();
|
||||
|
||||
BOOST_CHECK_EQUAL( poly.OutlineCount(), 1 );
|
||||
BOOST_CHECK_EQUAL( poly.Outline( 0 ).PointCount(), 7 );
|
||||
BOOST_CHECK( GEOM_TEST::IsPolySetValid( poly ) );
|
||||
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
Loading…
x
Reference in New Issue
Block a user