Add self-intersection check algo for arcs in SHAPE_LINE_CHAIN.

This commit is contained in:
Alex Shvartzkop 2024-04-23 19:21:23 +03:00
parent fcdaa4a59e
commit 5e70883ef6
2 changed files with 238 additions and 3 deletions

View File

@ -3,7 +3,7 @@
*
* Copyright (C) 2013 CERN
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* Copyright (C) 2013-2022 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2013-2024 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
@ -648,12 +648,19 @@ public:
bool CheckClearance( const VECTOR2I& aP, const int aDist) const;
/**
* Check if the line chain is self-intersecting.
* Check if the line chain is self-intersecting. Only processes line segments (not arcs).
*
* @return (optional) first found self-intersection point.
*/
const std::optional<INTERSECTION> SelfIntersecting() const;
/**
* Check if the line chain is self-intersecting. Also processes arcs. Might be slower.
*
* @return (optional) first found self-intersection point.
*/
const std::optional<INTERSECTION> SelfIntersectingWithArcs() const;
/**
* Find the segment nearest the given point.
*

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2017 CERN
* Copyright (C) 2013-2022 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2013-2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
*
@ -32,6 +32,7 @@
#include <clipper.hpp>
#include <clipper2/clipper.h>
#include <core/kicad_algo.h> // for alg::run_on_pair
#include <geometry/circle.h>
#include <geometry/seg.h> // for SEG, OPT_VECTOR2I
#include <geometry/shape_line_chain.h>
#include <geometry/shape_poly_set.h>
@ -1825,6 +1826,233 @@ const std::optional<SHAPE_LINE_CHAIN::INTERSECTION> SHAPE_LINE_CHAIN::SelfInters
}
struct BOX2I_MINMAX
{
BOX2I_MINMAX() : m_Left( 0 ), m_Top( 0 ), m_Right( 0 ), m_Bottom( 0 ) {}
BOX2I_MINMAX( int aLeft, int aTop, int aRight, int aBottom ) :
m_Left( aLeft ), m_Top( aTop ), m_Right( aRight ), m_Bottom( aBottom )
{
}
BOX2I_MINMAX( const BOX2I& aBox ) :
m_Left( aBox.GetLeft() ), m_Top( aBox.GetTop() ), m_Right( aBox.GetRight() ),
m_Bottom( aBox.GetBottom() )
{
}
BOX2I_MINMAX( const SHAPE_ARC& aArc ) : BOX2I_MINMAX( aArc.BBox() ) {}
BOX2I_MINMAX( const SEG& aSeg )
{
m_Left = std::min( aSeg.A.x, aSeg.B.x );
m_Right = std::max( aSeg.A.x, aSeg.B.x );
m_Top = std::min( aSeg.A.y, aSeg.B.y );
m_Bottom = std::max( aSeg.A.y, aSeg.B.y );
}
inline bool Intersects( const BOX2I_MINMAX& aOther ) const
{
// calculate the left common area coordinate:
int left = std::max( m_Left, aOther.m_Left );
// calculate the right common area coordinate:
int right = std::min( m_Right, aOther.m_Right );
// calculate the upper common area coordinate:
int top = std::max( m_Top, aOther.m_Top );
// calculate the lower common area coordinate:
int bottom = std::min( m_Bottom, aOther.m_Bottom );
// if a common area exists, it must have a positive (null accepted) size
return left <= right && top <= bottom;
}
int m_Left;
int m_Top;
int m_Right;
int m_Bottom;
};
struct SHAPE_KEY
{
SHAPE_KEY( int aFirstIdx, int aArcIdx, const BOX2I_MINMAX& aBBox ) :
m_FirstIdx( aFirstIdx ), m_ArcIdx( aArcIdx ), m_BBox( aBBox )
{
}
int m_FirstIdx;
int m_ArcIdx;
BOX2I_MINMAX m_BBox;
};
const std::optional<SHAPE_LINE_CHAIN::INTERSECTION>
SHAPE_LINE_CHAIN::SelfIntersectingWithArcs() const
{
auto pointsClose = []( const VECTOR2I& aPt1, const VECTOR2I& aPt2 ) -> bool
{
return ( VECTOR2D( aPt1 ) - aPt2 ).SquaredEuclideanNorm() <= 2.0;
};
auto collideArcSeg = [&pointsClose]( const SHAPE_ARC& aArc, const SEG& aSeg, int aClearance = 0,
VECTOR2I* aLocation = nullptr )
{
VECTOR2I center = aArc.GetCenter();
CIRCLE circle( center, aArc.GetRadius() );
std::vector<VECTOR2I> candidatePts = circle.Intersect( aSeg );
for( const VECTOR2I& candidate : candidatePts )
{
// Skip shared points
if( aArc.GetP1() == aSeg.A && pointsClose( candidate, aSeg.A ) )
continue;
if( aSeg.B == aArc.GetP0() && pointsClose( candidate, aSeg.B ) )
continue;
bool collides = aArc.Collide( candidate, aClearance, nullptr, aLocation );
if( collides )
return true;
}
return false;
};
auto collideArcArc = [&pointsClose]( const SHAPE_ARC& aArc1, const SHAPE_ARC& aArc2,
VECTOR2I* aLocation = nullptr )
{
std::vector<VECTOR2I> candidatePts;
aArc1.Intersect( aArc2, &candidatePts );
for( const VECTOR2I& candidate : candidatePts )
{
// Skip shared points
if( aArc1.GetP1() == aArc2.GetP0() && pointsClose( candidate, aArc1.GetP1() ) )
continue;
if( aArc2.GetP1() == aArc1.GetP0() && pointsClose( candidate, aArc2.GetP1() ) )
continue;
if( aLocation )
*aLocation = candidate;
return true;
}
return false;
};
auto collideSegSeg = [this]( int s1, int s2, INTERSECTION& is )
{
SEG seg1 = CSegment( s1 );
SEG seg2 = CSegment( s2 );
const VECTOR2I s2a = seg2.A, s2b = seg2.B;
if( s1 + 1 != s2 && seg1.Contains( s2a ) )
{
is.index_our = s1;
is.index_their = s2;
is.p = s2a;
return true;
}
else if( seg1.Contains( s2b ) &&
// for closed polylines, the ending point of the
// last segment == starting point of the first segment
// this is a normal case, not self intersecting case
!( IsClosed() && s1 == 0 && s2 == SegmentCount() - 1 ) )
{
is.index_our = s1;
is.index_their = s2;
is.p = s2b;
return true;
}
else
{
OPT_VECTOR2I p = seg1.Intersect( seg2, true );
if( p )
{
is.index_our = s1;
is.index_their = s2;
is.p = *p;
return true;
}
}
return false;
};
INTERSECTION is;
std::vector<SHAPE_KEY> shapeCache;
for( int si = 0; si != -1; si = NextShape( si ) )
{
int arci = ArcIndex( si );
shapeCache.emplace_back( si, arci,
arci == -1 ? BOX2I_MINMAX( CSegment( si ) )
: BOX2I_MINMAX( Arc( arci ) ) );
}
for( size_t sk1 = 0; sk1 < shapeCache.size(); sk1++ )
{
for( size_t sk2 = sk1 + 1; sk2 < shapeCache.size(); sk2++ )
{
VECTOR2I loc;
const SHAPE_KEY& k1 = shapeCache[sk1];
const SHAPE_KEY& k2 = shapeCache[sk2];
if( !k1.m_BBox.Intersects( k2.m_BBox ) )
continue;
if( k1.m_ArcIdx == -1 && k2.m_ArcIdx == -1 )
{
if( collideSegSeg( k1.m_FirstIdx, k2.m_FirstIdx, is ) )
{
return is;
}
}
else if( k1.m_ArcIdx != -1 && k2.m_ArcIdx == -1 )
{
if( collideArcSeg( Arc( k1.m_ArcIdx ), CSegment( k2.m_FirstIdx ), 0, &loc ) )
{
is.index_our = k1.m_FirstIdx;
is.index_their = k2.m_FirstIdx;
is.p = loc;
return is;
}
}
else if( k1.m_ArcIdx == -1 && k2.m_ArcIdx != -1 )
{
if( collideArcSeg( Arc( k2.m_ArcIdx ), CSegment( k1.m_FirstIdx ), 0, &loc ) )
{
is.index_our = k1.m_FirstIdx;
is.index_their = k2.m_FirstIdx;
is.p = loc;
return is;
}
}
else if( k1.m_ArcIdx != -1 && k2.m_ArcIdx != -1 )
{
if( collideArcArc( Arc( k1.m_ArcIdx ), Arc( k2.m_ArcIdx ), &loc ) )
{
is.index_our = k1.m_FirstIdx;
is.index_their = k2.m_FirstIdx;
is.p = loc;
return is;
}
}
}
}
return std::optional<SHAPE_LINE_CHAIN::INTERSECTION>();
}
const VECTOR2I SHAPE_LINE_CHAIN::NearestPoint( const VECTOR2I& aP,
bool aAllowInternalShapePoints ) const
{